diff --git a/Android.bp b/Android.bp index c636adedf636fb9052cd7c038e804a497229be94..892a48a46da72dad22b630d8d716b4558495ac5c 100644 --- a/Android.bp +++ b/Android.bp @@ -83,13 +83,14 @@ java_library { "android.hardware.radio-V1.4-java", "android.hardware.radio-V1.5-java", "android.hardware.radio-V1.6-java", - "android.hardware.radio.config-V1-java", - "android.hardware.radio.data-V1-java", - "android.hardware.radio.messaging-V1-java", - "android.hardware.radio.modem-V1-java", - "android.hardware.radio.network-V1-java", - "android.hardware.radio.sim-V1-java", - "android.hardware.radio.voice-V1-java", + "android.hardware.radio.config-V2-java", + "android.hardware.radio.data-V2-java", + "android.hardware.radio.ims-V1-java", + "android.hardware.radio.messaging-V2-java", + "android.hardware.radio.modem-V2-java", + "android.hardware.radio.network-V2-java", + "android.hardware.radio.sim-V2-java", + "android.hardware.radio.voice-V2-java", "voip-common", "ims-common", "unsupportedappusage", diff --git a/OWNERS b/OWNERS index de9ceedc18b3a5ae7ba5da6d749a291c112451ac..35f2818e6e5f64f4a025da7f30d36f5825a00e53 100644 --- a/OWNERS +++ b/OWNERS @@ -17,12 +17,6 @@ tjstuart@google.com tnd@google.com xiaotonj@google.com -# Temporarily reduced the owner during refactoring -per-file SubscriptionController.java=set noparent -per-file SubscriptionController.java=jackyu@google.com,amruthr@google.com -per-file SubscriptionInfoUpdater.java=set noparent -per-file SubscriptionInfoUpdater.java=jackyu@google.com,amruthr@google.com - diff --git a/README.txt b/README.txt index 9e40b7713d9e98d085b75a11664ed83951ce6c0a..1a44beb9e04c42baaf432f065c3d6b13f5c9f783 100644 --- a/README.txt +++ b/README.txt @@ -15,7 +15,7 @@ We define several AIDL interfaces in frameworks/base/telephony/ which we implement in this directory and packages/services/Telephony. This IPC scheme allows us to run public API code in the calling process, while the telephony-related code runs in the privileged com.android.phone process. Such -implementations include PhoneInterfaceManager, SubscriptionController and +implementations include PhoneInterfaceManager, SubscriptionManagerService and others. The declaration of the com.android.phone process is in diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto index 0f87e954dc9003186a79446bf3b9aef82e77ab95..61e44a3c8fe6bd44fde401905f6e02448b5870b6 100644 --- a/proto/src/persist_atoms.proto +++ b/proto/src/persist_atoms.proto @@ -23,7 +23,7 @@ option java_outer_classname = "PersistAtomsProto"; // Holds atoms to store on persist storage in case of power cycle or process crash. // NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation. -// Next id: 53 +// Next id: 70 message PersistAtoms { /* Aggregated RAT usage during the call. */ repeated VoiceCallRatUsage voice_call_rat_usage = 1; @@ -180,6 +180,57 @@ message PersistAtoms { /* Unmetered networks information. */ repeated UnmeteredNetworks unmetered_networks = 52; + + /* Outgoing Short Code SMS statistics and information. */ + repeated OutgoingShortCodeSms outgoing_short_code_sms = 53; + + /* Timestamp of last outgoing_short_code_sms pull. */ + optional int64 outgoing_short_code_sms_pull_timestamp_millis = 54; + + /* Number of time the user toggled the data switch feature since the last collection. */ + optional int32 auto_data_switch_toggle_count = 55; + + /** Snapshot of satellite controller. */ + repeated SatelliteController satellite_controller = 58; + + /* Timestamp of last satellite_controller pull. */ + optional int64 satellite_controller_pull_timestamp_millis = 59; + + /** Snapshot of satellite controller. */ + repeated SatelliteSession satellite_session = 60; + + /* Timestamp of last satellite_controller pull. */ + optional int64 satellite_session_pull_timestamp_millis = 61; + + /** Snapshot of satellite incoming datagram. */ + repeated SatelliteIncomingDatagram satellite_incoming_datagram = 62; + + /* Timestamp of last satellite_incoming_datagram pull. */ + optional int64 satellite_incoming_datagram_pull_timestamp_millis = 63; + + /** Snapshot of satellite outgoing datagram. */ + repeated SatelliteOutgoingDatagram satellite_outgoing_datagram = 64; + + /* Timestamp of last satellite_outgoing_datagram pull. */ + optional int64 satellite_outgoing_datagram_pull_timestamp_millis = 65; + + /** Snapshot of satellite provision datagram. */ + repeated SatelliteProvision satellite_provision = 66; + + /* Timestamp of last satellite_provision pull. */ + optional int64 satellite_provision_pull_timestamp_millis = 67; + + /** Snapshot of satellite SOS message recommender. */ + repeated SatelliteSosMessageRecommender satellite_sos_message_recommender = 68; + + /* Timestamp of last satellite_sos_message_recommender pull. */ + optional int64 satellite_sos_message_recommender_pull_timestamp_millis = 69; + + /* Consolidated emergency numbers list information. */ + repeated EmergencyNumbersInfo emergency_numbers_info = 56; + + /* Timestamp of last emergency number pull. */ + optional int64 emergency_number_pull_timestamp_millis = 57; } // The canonical versions of the following enums live in: @@ -223,6 +274,8 @@ message VoiceCallSession { optional bool is_multiparty = 31; optional int32 call_duration = 32; optional int32 last_known_rat = 33; + optional int32 fold_state = 34; + // Internal use only optional int64 setup_begin_millis = 10001; } @@ -250,6 +303,7 @@ message IncomingSms { optional int32 carrier_id = 13; optional int64 message_id = 14; optional int32 count = 15; + optional bool is_managed_profile = 16; // Internal use only optional int32 hashCode = 10001; @@ -271,6 +325,9 @@ message OutgoingSms { optional int32 retry_id = 13; optional int64 interval_millis = 14; optional int32 count = 15; + optional int32 send_error_code = 16; + optional int32 network_error_code = 17; + optional bool is_managed_profile = 18; // Internal use only optional int32 hashCode = 10001; @@ -319,6 +376,8 @@ message CellularServiceState { optional int32 carrier_id = 8; optional int64 total_time_millis = 9; // Duration needs to be rounded when pulled optional bool is_emergency_only = 10; + optional bool is_internet_pdn_up = 11; + optional int32 fold_state = 12; // Internal use only optional int64 last_used_millis = 10001; @@ -524,3 +583,92 @@ message OutgoingShortCodeSms { optional int32 xml_version = 2; optional int32 short_code_sms_count = 3; } + +message SatelliteController { + optional int32 count_of_satellite_service_enablements_success = 1; + optional int32 count_of_satellite_service_enablements_fail = 2; + optional int32 count_of_outgoing_datagram_success = 3; + optional int32 count_of_outgoing_datagram_fail = 4; + optional int32 count_of_incoming_datagram_success = 5; + optional int32 count_of_incoming_datagram_fail = 6; + optional int32 count_of_datagram_type_sos_sms_success = 7; + optional int32 count_of_datagram_type_sos_sms_fail = 8; + optional int32 count_of_datagram_type_location_sharing_success = 9; + optional int32 count_of_datagram_type_location_sharing_fail = 10; + optional int32 count_of_provision_success = 11; + optional int32 count_of_provision_fail = 12; + optional int32 count_of_deprovision_success = 13; + optional int32 count_of_deprovision_fail = 14; + optional int32 total_service_uptime_sec = 15; + optional int32 total_battery_consumption_percent = 16; + optional int32 total_battery_charged_time_sec = 17; +} + +message SatelliteSession { + optional int32 satellite_service_initialization_result = 1; + optional int32 satellite_technology = 2; + optional int32 count = 3; +} + +message SatelliteIncomingDatagram { + optional int32 result_code = 1; + optional int32 datagram_size_bytes = 2; + optional int64 datagram_transfer_time_millis = 3; +} + +message SatelliteOutgoingDatagram { + optional int32 datagram_type = 1; + optional int32 result_code = 2; + optional int32 datagram_size_bytes = 3; + optional int64 datagram_transfer_time_millis = 4; +} + +message SatelliteProvision { + optional int32 result_code = 1; + optional int32 provisioning_time_sec = 2; + optional bool is_provision_request = 3; + optional bool is_canceled = 4; +} + +message SatelliteSosMessageRecommender { + optional bool is_display_sos_message_sent = 1; + optional int32 count_of_timer_started = 2; + optional bool is_ims_registered = 3; + optional int32 cellular_service_state = 4; + optional int32 count = 5; +} + +message EmergencyNumbersInfo { + enum ServiceCategory { + EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED = 0; + EMERGENCY_SERVICE_CATEGORY_POLICE = 1; + EMERGENCY_SERVICE_CATEGORY_AMBULANCE = 2; + EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE = 3; + EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD = 4; + EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE = 5; + EMERGENCY_SERVICE_CATEGORY_MIEC = 6; + EMERGENCY_SERVICE_CATEGORY_AIEC = 7; + } + enum Source { + EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING = 0; + EMERGENCY_NUMBER_SOURCE_SIM = 1; + EMERGENCY_NUMBER_SOURCE_DATABASE = 2; + EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = 3; + EMERGENCY_NUMBER_SOURCE_DEFAULT = 4; + } + enum CallRoute { + EMERGENCY_CALL_ROUTE_UNKNOWN = 0; + EMERGENCY_CALL_ROUTE_EMERGENCY = 1; + EMERGENCY_CALL_ROUTE_NORMAL = 2; + } + optional bool is_db_version_ignored = 1; + optional int32 asset_version = 2; + optional int32 ota_version = 3; + optional string number = 4; + optional string country_iso = 5; + optional string mnc = 6; + optional CallRoute route = 7; + repeated string urns = 8; + repeated ServiceCategory service_categories = 9; + repeated Source sources = 10; +} diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java index 972884a77be7391e4864f3f0bcae41df9ff124b0..b8de97522e71867f241cf80bfdf60925a1db7f2b 100644 --- a/src/java/com/android/internal/telephony/BaseCommands.java +++ b/src/java/com/android/internal/telephony/BaseCommands.java @@ -17,6 +17,8 @@ package com.android.internal.telephony; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.AsyncResult; @@ -26,6 +28,7 @@ import android.os.Message; import android.os.Registrant; import android.os.RegistrantList; import android.telephony.Annotation.RadioPowerState; +import android.telephony.BarringInfo; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; @@ -114,6 +117,17 @@ public abstract class BaseCommands implements CommandsInterface { protected RegistrantList mBarringInfoChangedRegistrants = new RegistrantList(); protected RegistrantList mSimPhonebookChangedRegistrants = new RegistrantList(); protected RegistrantList mSimPhonebookRecordsReceivedRegistrants = new RegistrantList(); + protected RegistrantList mEmergencyNetworkScanRegistrants = new RegistrantList(); + protected RegistrantList mConnectionSetupFailureRegistrants = new RegistrantList(); + protected RegistrantList mNotifyAnbrRegistrants = new RegistrantList(); + protected RegistrantList mTriggerImsDeregistrationRegistrants = new RegistrantList(); + protected RegistrantList mPendingSatelliteMessageCountRegistrants = new RegistrantList(); + protected RegistrantList mNewSatelliteMessagesRegistrants = new RegistrantList(); + protected RegistrantList mSatelliteMessagesTransferCompleteRegistrants = new RegistrantList(); + protected RegistrantList mSatellitePointingInfoChangedRegistrants = new RegistrantList(); + protected RegistrantList mSatelliteModeChangedRegistrants = new RegistrantList(); + protected RegistrantList mSatelliteRadioTechnologyChangedRegistrants = new RegistrantList(); + protected RegistrantList mSatelliteProvisionStateChangedRegistrants = new RegistrantList(); @UnsupportedAppUsage protected Registrant mGsmSmsRegistrant; @@ -160,6 +174,8 @@ public abstract class BaseCommands implements CommandsInterface { // Cache last emergency number list indication from radio private final List mLastEmergencyNumberListIndication = new ArrayList<>(); + // The last barring information received + protected BarringInfo mLastBarringInfo = new BarringInfo(); // Preferred network type received from PhoneFactory. // This is used when establishing a connection to the // vendor ril so it starts up in the correct mode. @@ -900,6 +916,7 @@ public abstract class BaseCommands implements CommandsInterface { || mState == TelephonyManager.RADIO_POWER_UNAVAILABLE) && (oldState == TelephonyManager.RADIO_POWER_ON)) { mOffOrNotAvailRegistrants.notifyRegistrants(); + mLastBarringInfo = new BarringInfo(); } } } @@ -918,6 +935,12 @@ public abstract class BaseCommands implements CommandsInterface { } } + /** {@inheritDoc} */ + @Override + public @NonNull BarringInfo getLastBarringInfo() { + return mLastBarringInfo; + } + /** * {@inheritDoc} */ @@ -1132,4 +1155,133 @@ public abstract class BaseCommands implements CommandsInterface { @Override public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) { } + + /** + * Register for Emergency network scan result. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + @Override + public void registerForEmergencyNetworkScan(Handler h, int what, Object obj) { + mEmergencyNetworkScanRegistrants.add(h, what, obj); + } + + /** + * Unregister for Emergency network scan result. + * + * @param h Handler to be removed from the registrant list. + */ + @Override + public void unregisterForEmergencyNetworkScan(Handler h) { + mEmergencyNetworkScanRegistrants.remove(h); + } + + @Override + public void registerForConnectionSetupFailure(Handler h, int what, Object obj) { + mConnectionSetupFailureRegistrants.addUnique(h, what, obj); + } + + @Override + public void unregisterForConnectionSetupFailure(Handler h) { + mConnectionSetupFailureRegistrants.remove(h); + } + + @Override + public void registerForNotifyAnbr(Handler h, int what, Object obj) { + mNotifyAnbrRegistrants.addUnique(h, what, obj); + } + + @Override + public void unregisterForNotifyAnbr(Handler h) { + mNotifyAnbrRegistrants.remove(h); + } + + @Override + public void registerForTriggerImsDeregistration(Handler h, int what, Object obj) { + mTriggerImsDeregistrationRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForTriggerImsDeregistration(Handler h) { + mTriggerImsDeregistrationRegistrants.remove(h); + } + + @Override + public void registerForPendingSatelliteMessageCount( + @NonNull Handler h, int what, @Nullable Object obj) { + mPendingSatelliteMessageCountRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForPendingSatelliteMessageCount(@NonNull Handler h) { + mPendingSatelliteMessageCountRegistrants.remove(h); + } + + @Override + public void registerForNewSatelliteMessages( + @NonNull Handler h, int what, @Nullable Object obj) { + mNewSatelliteMessagesRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForNewSatelliteMessages(@NonNull Handler h) { + mNewSatelliteMessagesRegistrants.remove(h); + } + + @Override + public void registerForSatelliteMessagesTransferComplete(@NonNull Handler h, + int what, @Nullable Object obj) { + mSatelliteMessagesTransferCompleteRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForSatelliteMessagesTransferComplete(@NonNull Handler h) { + mSatelliteMessagesTransferCompleteRegistrants.remove(h); + } + + @Override + public void registerForSatellitePointingInfoChanged(@NonNull Handler h, + int what, @Nullable Object obj) { + mSatellitePointingInfoChangedRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForSatellitePointingInfoChanged(@NonNull Handler h) { + mSatellitePointingInfoChangedRegistrants.remove(h); + } + + @Override + public void registerForSatelliteModeChanged(@NonNull Handler h, + int what, @Nullable Object obj) { + mSatelliteModeChangedRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForSatelliteModeChanged(@NonNull Handler h) { + mSatelliteModeChangedRegistrants.remove(h); + } + + @Override + public void registerForSatelliteRadioTechnologyChanged(@NonNull Handler h, + int what, @Nullable Object obj) { + mSatelliteRadioTechnologyChangedRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForSatelliteRadioTechnologyChanged(@NonNull Handler h) { + mSatelliteRadioTechnologyChangedRegistrants.remove(h); + } + + @Override + public void registerForSatelliteProvisionStateChanged(@NonNull Handler h, + int what, @Nullable Object obj) { + mSatelliteProvisionStateChangedRegistrants.add(h, what, obj); + } + + @Override + public void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) { + mSatelliteProvisionStateChangedRegistrants.remove(h); + } } diff --git a/src/java/com/android/internal/telephony/CallWaitingController.java b/src/java/com/android/internal/telephony/CallWaitingController.java new file mode 100644 index 0000000000000000000000000000000000000000..49940fc65da9ea91431c819e25e662e40cf1dcc7 --- /dev/null +++ b/src/java/com/android/internal/telephony/CallWaitingController.java @@ -0,0 +1,684 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_CHANGE; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_POWER_UP; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_IMS_ONLY; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_NONE; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_USER_CHANGE; +import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL; +import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT; +import static android.telephony.CarrierConfigManager.ImsSs.KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY; +import static android.telephony.CarrierConfigManager.ImsSs.SUPPLEMENTARY_SERVICE_CW; + +import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE; +import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; +import com.android.telephony.Rlog; + +import java.io.PrintWriter; + +/** + * Controls the change of the user setting of the call waiting service + */ +public class CallWaitingController extends Handler { + + public static final String LOG_TAG = "CallWaitingCtrl"; + private static final boolean DBG = false; /* STOPSHIP if true */ + + // Terminal-based call waiting is not supported. */ + public static final int TERMINAL_BASED_NOT_SUPPORTED = -1; + // Terminal-based call waiting is supported but not activated. */ + public static final int TERMINAL_BASED_NOT_ACTIVATED = 0; + // Terminal-based call waiting is supported and activated. */ + public static final int TERMINAL_BASED_ACTIVATED = 1; + + private static final int EVENT_SET_CALL_WAITING_DONE = 1; + private static final int EVENT_GET_CALL_WAITING_DONE = 2; + private static final int EVENT_REGISTERED_TO_NETWORK = 3; + + // Class to pack mOnComplete object passed by the caller + private static class Cw { + final boolean mEnable; + final Message mOnComplete; + final boolean mImsRegistered; + + Cw(boolean enable, boolean imsRegistered, Message onComplete) { + mEnable = enable; + mOnComplete = onComplete; + mImsRegistered = imsRegistered; + } + } + + @VisibleForTesting + public static final String PREFERENCE_TBCW = "terminal_based_call_waiting"; + @VisibleForTesting + public static final String KEY_SUB_ID = "subId"; + @VisibleForTesting + public static final String KEY_STATE = "state"; + @VisibleForTesting + public static final String KEY_CS_SYNC = "cs_sync"; + + private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener = + (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigurationChanged( + slotIndex); + + private boolean mSupportedByImsService = false; + private boolean mValidSubscription = false; + + // The user's last setting of terminal-based call waiting + private int mCallWaitingState = TERMINAL_BASED_NOT_SUPPORTED; + + private int mSyncPreference = CALL_WAITING_SYNC_NONE; + private int mLastSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + private boolean mCsEnabled = false; + private boolean mRegisteredForNetworkAttach = false; + private boolean mImsRegistered = false; + + private final GsmCdmaPhone mPhone; + private final ServiceStateTracker mSST; + private final Context mContext; + + // Constructors + public CallWaitingController(GsmCdmaPhone phone) { + mPhone = phone; + mSST = phone.getServiceStateTracker(); + mContext = phone.getContext(); + } + + private void initialize() { + CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class); + if (ccm != null) { + // Callback directly handle carrier config change should be executed in handler thread + ccm.registerCarrierConfigChangeListener(this::post, mCarrierConfigChangeListener); + } else { + loge("CarrierConfigLoader is not available."); + } + + int phoneId = mPhone.getPhoneId(); + int subId = mPhone.getSubId(); + SharedPreferences sp = + mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE); + mLastSubId = sp.getInt(KEY_SUB_ID + phoneId, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mCallWaitingState = sp.getInt(KEY_STATE + subId, TERMINAL_BASED_NOT_SUPPORTED); + mSyncPreference = sp.getInt(KEY_CS_SYNC + phoneId, CALL_WAITING_SYNC_NONE); + + logi("initialize phoneId=" + phoneId + + ", lastSubId=" + mLastSubId + ", subId=" + subId + + ", state=" + mCallWaitingState + ", sync=" + mSyncPreference + + ", csEnabled=" + mCsEnabled); + } + + /** + * Returns the cached user setting. + * + * Possible values are + * {@link #TERMINAL_BASED_NOT_SUPPORTED}, + * {@link #TERMINAL_BASED_NOT_ACTIVATED}, and + * {@link #TERMINAL_BASED_ACTIVATED}. + * + * @param forCsOnly indicates the caller expects the result for CS calls only + */ + @VisibleForTesting + public synchronized int getTerminalBasedCallWaitingState(boolean forCsOnly) { + if (forCsOnly && (!mImsRegistered) && mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) { + return TERMINAL_BASED_NOT_SUPPORTED; + } + if (!mValidSubscription) return TERMINAL_BASED_NOT_SUPPORTED; + return mCallWaitingState; + } + + /** + * Serves the user's requests to interrogate the call waiting service + * + * @return true when terminal-based call waiting is supported, otherwise false + */ + @VisibleForTesting + public synchronized boolean getCallWaiting(@Nullable Message onComplete) { + if (mCallWaitingState == TERMINAL_BASED_NOT_SUPPORTED) return false; + + logi("getCallWaiting " + mCallWaitingState); + + if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) { + // Interrogate CW in CS network + if (!mCsEnabled) { + // skip interrogation if CS is not available and IMS is registered + if (isCircuitSwitchedNetworkAvailable() || !isImsRegistered()) { + Cw cw = new Cw(false, isImsRegistered(), onComplete); + Message resp = obtainMessage(EVENT_GET_CALL_WAITING_DONE, 0, 0, cw); + mPhone.mCi.queryCallWaiting(SERVICE_CLASS_NONE, resp); + return true; + } + } + } + + if (mSyncPreference == CALL_WAITING_SYNC_NONE + || mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE + || mSyncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP + || isSyncImsOnly()) { + sendGetCallWaitingResponse(onComplete); + return true; + } else if (mSyncPreference == CALL_WAITING_SYNC_USER_CHANGE + || mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) { + Cw cw = new Cw(false, isImsRegistered(), onComplete); + Message resp = obtainMessage(EVENT_GET_CALL_WAITING_DONE, 0, 0, cw); + mPhone.mCi.queryCallWaiting(SERVICE_CLASS_NONE, resp); + return true; + } + + return false; + } + + /** + * Serves the user's requests to set the call waiting service + * + * @param serviceClass the target service class. Values are CommandsInterface.SERVICE_CLASS_*. + * @return true when terminal-based call waiting is supported, otherwise false + */ + @VisibleForTesting + public synchronized boolean setCallWaiting(boolean enable, + int serviceClass, @Nullable Message onComplete) { + if (mCallWaitingState == TERMINAL_BASED_NOT_SUPPORTED) return false; + + if ((serviceClass & SERVICE_CLASS_VOICE) != SERVICE_CLASS_VOICE) return false; + + logi("setCallWaiting enable=" + enable + ", service=" + serviceClass); + + if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) { + // Enable CW in the CS network + if (!mCsEnabled && enable) { + if (isCircuitSwitchedNetworkAvailable() || !isImsRegistered()) { + Cw cw = new Cw(true, isImsRegistered(), onComplete); + Message resp = obtainMessage(EVENT_SET_CALL_WAITING_DONE, 0, 0, cw); + mPhone.mCi.setCallWaiting(true, serviceClass, resp); + return true; + } else { + // CS network is not available, however, IMS is registered. + // Enabling the service in the CS network will be delayed. + registerForNetworkAttached(); + } + } + } + + if (mSyncPreference == CALL_WAITING_SYNC_NONE + || mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE + || mSyncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP + || isSyncImsOnly()) { + updateState( + enable ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED); + + sendToTarget(onComplete, null, null); + return true; + } else if (mSyncPreference == CALL_WAITING_SYNC_USER_CHANGE + || mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) { + Cw cw = new Cw(enable, isImsRegistered(), onComplete); + Message resp = obtainMessage(EVENT_SET_CALL_WAITING_DONE, 0, 0, cw); + mPhone.mCi.setCallWaiting(enable, serviceClass, resp); + return true; + } + + return false; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_SET_CALL_WAITING_DONE: + onSetCallWaitingDone((AsyncResult) msg.obj); + break; + case EVENT_GET_CALL_WAITING_DONE: + onGetCallWaitingDone((AsyncResult) msg.obj); + break; + case EVENT_REGISTERED_TO_NETWORK: + onRegisteredToNetwork(); + break; + default: + break; + } + } + + private synchronized void onSetCallWaitingDone(AsyncResult ar) { + if (ar.userObj == null) { + // For the case, CALL_WAITING_SYNC_FIRST_POWER_UP + if (DBG) logd("onSetCallWaitingDone to sync on network attached"); + if (ar.exception == null) { + updateSyncState(true); + } else { + loge("onSetCallWaitingDone e=" + ar.exception); + } + return; + } + + if (!(ar.userObj instanceof Cw)) { + // Unexpected state + if (DBG) logd("onSetCallWaitingDone unexpected result"); + return; + } + + if (DBG) logd("onSetCallWaitingDone"); + Cw cw = (Cw) ar.userObj; + + if (mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) { + // do not synchronize service state between CS and IMS + sendToTarget(cw.mOnComplete, ar.result, ar.exception); + return; + } + + if (ar.exception == null) { + if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) { + // SYNC_FIRST_CHANGE implies cw.mEnable is true. + updateSyncState(true); + } + updateState( + cw.mEnable ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED); + } else if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) { + if (cw.mImsRegistered) { + // IMS is registered. Do not notify error. + // SYNC_FIRST_CHANGE implies cw.mEnable is true. + updateState(TERMINAL_BASED_ACTIVATED); + sendToTarget(cw.mOnComplete, null, null); + return; + } + } + sendToTarget(cw.mOnComplete, ar.result, ar.exception); + } + + private synchronized void onGetCallWaitingDone(AsyncResult ar) { + if (ar.userObj == null) { + // For the case, CALL_WAITING_SYNC_FIRST_POWER_UP + if (DBG) logd("onGetCallWaitingDone to sync on network attached"); + boolean enabled = false; + if (ar.exception == null) { + //resp[0]: 1 if enabled, 0 otherwise + //resp[1]: bitwise ORs of SERVICE_CLASS_* constants + int[] resp = (int[]) ar.result; + if (resp != null && resp.length > 1) { + enabled = (resp[0] == 1) + && (resp[1] & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE; + } else { + loge("onGetCallWaitingDone unexpected response"); + } + } else { + loge("onGetCallWaitingDone e=" + ar.exception); + } + if (enabled) { + updateSyncState(true); + } else { + logi("onGetCallWaitingDone enabling CW service in CS network"); + mPhone.mCi.setCallWaiting(true, SERVICE_CLASS_VOICE, + obtainMessage(EVENT_SET_CALL_WAITING_DONE)); + } + unregisterForNetworkAttached(); + return; + } + + if (!(ar.userObj instanceof Cw)) { + // Unexpected state + if (DBG) logd("onGetCallWaitingDone unexpected result"); + return; + } + + if (DBG) logd("onGetCallWaitingDone"); + Cw cw = (Cw) ar.userObj; + + if (mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) { + // do not synchronize service state between CS and IMS + sendToTarget(cw.mOnComplete, ar.result, ar.exception); + return; + } + + if (ar.exception == null) { + int[] resp = (int[]) ar.result; + //resp[0]: 1 if enabled, 0 otherwise + //resp[1]: bitwise ORs of SERVICE_CLASS_ + if (resp == null || resp.length < 2) { + logi("onGetCallWaitingDone unexpected response"); + if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) { + // no exception but unexpected response, local setting is preferred. + sendGetCallWaitingResponse(cw.mOnComplete); + } else { + sendToTarget(cw.mOnComplete, ar.result, ar.exception); + } + return; + } + + boolean enabled = resp[0] == 1 + && (resp[1] & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE; + + if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) { + updateSyncState(enabled); + + if (!enabled && !cw.mImsRegistered) { + // IMS is not registered, change the local setting + logi("onGetCallWaitingDone CW in CS network is disabled."); + updateState(TERMINAL_BASED_NOT_ACTIVATED); + } + + // return the user setting saved + sendGetCallWaitingResponse(cw.mOnComplete); + return; + } + updateState(enabled ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED); + } else if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) { + // Got an exception + if (cw.mImsRegistered) { + // queryCallWaiting failed. However, IMS is registered. Do not notify error. + // return the user setting saved + logi("onGetCallWaitingDone get an exception, but IMS is registered"); + sendGetCallWaitingResponse(cw.mOnComplete); + return; + } + } + sendToTarget(cw.mOnComplete, ar.result, ar.exception); + } + + private void sendToTarget(Message onComplete, Object result, Throwable exception) { + if (onComplete != null) { + AsyncResult.forMessage(onComplete, result, exception); + onComplete.sendToTarget(); + } + } + + private void sendGetCallWaitingResponse(Message onComplete) { + if (onComplete != null) { + int serviceClass = SERVICE_CLASS_NONE; + if (mCallWaitingState == TERMINAL_BASED_ACTIVATED) { + serviceClass = SERVICE_CLASS_VOICE; + } + sendToTarget(onComplete, new int[] { mCallWaitingState, serviceClass }, null); + } + } + + private synchronized void onRegisteredToNetwork() { + if (mCsEnabled) return; + + if (DBG) logd("onRegisteredToNetwork"); + + mPhone.mCi.queryCallWaiting(SERVICE_CLASS_NONE, + obtainMessage(EVENT_GET_CALL_WAITING_DONE)); + } + + private synchronized void onCarrierConfigurationChanged(int slotIndex) { + if (slotIndex != mPhone.getPhoneId()) return; + + int subId = mPhone.getSubId(); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + logi("onCarrierConfigChanged invalid subId=" + subId); + + mValidSubscription = false; + unregisterForNetworkAttached(); + return; + } + + if (!updateCarrierConfig(subId, false /* ignoreSavedState */)) { + return; + } + + logi("onCarrierConfigChanged cs_enabled=" + mCsEnabled); + + if (mSyncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP) { + if (!mCsEnabled) { + registerForNetworkAttached(); + } + } + } + + /** + * @param ignoreSavedState only used for test + * @return true when succeeded. + */ + @VisibleForTesting + public boolean updateCarrierConfig(int subId, boolean ignoreSavedState) { + mValidSubscription = true; + + PersistableBundle b = + CarrierConfigManager.getCarrierConfigSubset( + mContext, + subId, + KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY, + KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT, + KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL); + if (b.isEmpty()) return false; + + boolean supportsTerminalBased = false; + int[] services = b.getIntArray(KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY); + if (services != null) { + for (int service : services) { + if (service == SUPPLEMENTARY_SERVICE_CW) { + supportsTerminalBased = true; + } + } + } + int syncPreference = b.getInt(KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT, + CALL_WAITING_SYNC_FIRST_CHANGE); + boolean activated = b.getBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL); + int defaultState = supportsTerminalBased + ? (activated ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED) + : TERMINAL_BASED_NOT_SUPPORTED; + int savedState = getSavedState(subId); + + if (DBG) { + logd("updateCarrierConfig phoneId=" + mPhone.getPhoneId() + + ", subId=" + subId + ", support=" + supportsTerminalBased + + ", sync=" + syncPreference + ", default=" + defaultState + + ", savedState=" + savedState); + } + + int desiredState = savedState; + + if (ignoreSavedState) { + desiredState = defaultState; + } else if ((mLastSubId != subId) + && (syncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP + || syncPreference == CALL_WAITING_SYNC_FIRST_CHANGE)) { + desiredState = defaultState; + } else { + if (defaultState == TERMINAL_BASED_NOT_SUPPORTED) { + desiredState = TERMINAL_BASED_NOT_SUPPORTED; + } else if (savedState == TERMINAL_BASED_NOT_SUPPORTED) { + desiredState = defaultState; + } + } + + updateState(desiredState, syncPreference, ignoreSavedState); + return true; + } + + private void updateState(int state) { + updateState(state, mSyncPreference, false); + } + + private void updateState(int state, int syncPreference, boolean ignoreSavedState) { + int subId = mPhone.getSubId(); + + if (mLastSubId == subId + && mCallWaitingState == state + && mSyncPreference == syncPreference + && (!ignoreSavedState)) { + return; + } + + int phoneId = mPhone.getPhoneId(); + + logi("updateState phoneId=" + phoneId + + ", subId=" + subId + ", state=" + state + + ", sync=" + syncPreference + ", ignoreSavedState=" + ignoreSavedState); + + SharedPreferences sp = + mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE); + + SharedPreferences.Editor editor = sp.edit(); + editor.putInt(KEY_SUB_ID + phoneId, subId); + editor.putInt(KEY_STATE + subId, state); + editor.putInt(KEY_CS_SYNC + phoneId, syncPreference); + editor.apply(); + + mCallWaitingState = state; + mLastSubId = subId; + mSyncPreference = syncPreference; + if (mLastSubId != subId) { + mCsEnabled = false; + } + + mPhone.setTerminalBasedCallWaitingStatus(mCallWaitingState); + } + + private int getSavedState(int subId) { + SharedPreferences sp = + mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE); + int state = sp.getInt(KEY_STATE + subId, TERMINAL_BASED_NOT_SUPPORTED); + + logi("getSavedState subId=" + subId + ", state=" + state); + + return state; + } + + private void updateSyncState(boolean enabled) { + int phoneId = mPhone.getPhoneId(); + + logi("updateSyncState phoneId=" + phoneId + ", enabled=" + enabled); + + mCsEnabled = enabled; + } + + /** + * @return whether the service is enabled in the CS network + */ + @VisibleForTesting + public boolean getSyncState() { + return mCsEnabled; + } + + private boolean isCircuitSwitchedNetworkAvailable() { + logi("isCircuitSwitchedNetworkAvailable=" + + (mSST.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)); + return mSST.getServiceState().getState() == ServiceState.STATE_IN_SERVICE; + } + + private boolean isImsRegistered() { + logi("isImsRegistered " + mImsRegistered); + return mImsRegistered; + } + + /** + * Sets the registration state of IMS service. + */ + public synchronized void setImsRegistrationState(boolean registered) { + logi("setImsRegistrationState prev=" + mImsRegistered + + ", new=" + registered); + mImsRegistered = registered; + } + + private void registerForNetworkAttached() { + logi("registerForNetworkAttached"); + if (mRegisteredForNetworkAttach) return; + + mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null); + mRegisteredForNetworkAttach = true; + } + + private void unregisterForNetworkAttached() { + logi("unregisterForNetworkAttached"); + if (!mRegisteredForNetworkAttach) return; + + mSST.unregisterForNetworkAttached(this); + removeMessages(EVENT_REGISTERED_TO_NETWORK); + mRegisteredForNetworkAttach = false; + } + + /** + * Sets whether the device supports the terminal-based call waiting. + * Only for test + */ + @VisibleForTesting + public synchronized void setTerminalBasedCallWaitingSupported(boolean supported) { + if (mSupportedByImsService == supported) return; + + logi("setTerminalBasedCallWaitingSupported " + supported); + + mSupportedByImsService = supported; + + if (supported) { + initialize(); + onCarrierConfigurationChanged(mPhone.getPhoneId()); + } else { + CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class); + if (ccm != null && mCarrierConfigChangeListener != null) { + ccm.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener); + } + updateState(TERMINAL_BASED_NOT_SUPPORTED); + } + } + + /** + * Notifies that the UE has attached to the network + * Only for test + */ + @VisibleForTesting + public void notifyRegisteredToNetwork() { + sendEmptyMessage(EVENT_REGISTERED_TO_NETWORK); + } + + private boolean isSyncImsOnly() { + return (mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY && mImsRegistered); + } + + /** + * Dump this instance into a readable format for dumpsys usage. + */ + public void dump(PrintWriter printWriter) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + pw.increaseIndent(); + pw.println("CallWaitingController:"); + pw.println(" mSupportedByImsService=" + mSupportedByImsService); + pw.println(" mValidSubscription=" + mValidSubscription); + pw.println(" mCallWaitingState=" + mCallWaitingState); + pw.println(" mSyncPreference=" + mSyncPreference); + pw.println(" mLastSubId=" + mLastSubId); + pw.println(" mCsEnabled=" + mCsEnabled); + pw.println(" mRegisteredForNetworkAttach=" + mRegisteredForNetworkAttach); + pw.println(" mImsRegistered=" + mImsRegistered); + pw.decreaseIndent(); + } + + private void loge(String msg) { + Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg); + } + + private void logi(String msg) { + Rlog.i(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg); + } + + private void logd(String msg) { + Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg); + } +} diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java index 3e2baa5a992cf561e01fd7ee00e6a099fe85d67e..863db93ad56117af71365b99ac52225a72f74f76 100644 --- a/src/java/com/android/internal/telephony/CarrierInfoManager.java +++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java @@ -287,16 +287,17 @@ public class CarrierInfoManager { return; } mLastAccessResetCarrierKey = now; - int[] subIds = context.getSystemService(SubscriptionManager.class) - .getSubscriptionIds(mPhoneId); - if (subIds == null || subIds.length < 1) { + + int subId = SubscriptionManager.getSubscriptionId(mPhoneId); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { Log.e(LOG_TAG, "Could not reset carrier keys, subscription for mPhoneId=" + mPhoneId); return; } + final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class) - .createForSubscriptionId(subIds[0]); + .createForSubscriptionId(subId); int carrierId = telephonyManager.getSimCarrierId(); - deleteCarrierInfoForImsiEncryption(context, subIds[0], carrierId); + deleteCarrierInfoForImsiEncryption(context, subId, carrierId); Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD); SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId); context.sendBroadcastAsUser(resetIntent, UserHandle.ALL); diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java index fc6afc0fdfde89ea9a562ceaedbb8d020236b79f..ab7ebc42d76e253e139cbbe57967a8490abd9b91 100644 --- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java +++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java @@ -78,6 +78,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -563,10 +564,10 @@ public class CarrierPrivilegesTracker extends Handler { List signatures = UiccAccessRule.getSignatures(pkg); for (Signature signature : signatures) { byte[] sha1 = UiccAccessRule.getCertHash(signature, SHA_1); - certs.add(IccUtils.bytesToHexString(sha1).toUpperCase()); + certs.add(IccUtils.bytesToHexString(sha1).toUpperCase(Locale.ROOT)); byte[] sha256 = UiccAccessRule.getCertHash(signature, SHA_256); - certs.add(IccUtils.bytesToHexString(sha256).toUpperCase()); + certs.add(IccUtils.bytesToHexString(sha256).toUpperCase(Locale.ROOT)); } mInstalledPackageCerts.put(pkg.packageName, certs); diff --git a/src/java/com/android/internal/telephony/CarrierResolver.java b/src/java/com/android/internal/telephony/CarrierResolver.java index c80251e49a6ac927ea5587e28345cfafc40e7dba..8a9b3e38d6280eb233a886d805781d706136ed68 100644 --- a/src/java/com/android/internal/telephony/CarrierResolver.java +++ b/src/java/com/android/internal/telephony/CarrierResolver.java @@ -55,6 +55,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; /** * CarrierResolver identifies the subscription carrier and returns a canonical carrier Id @@ -192,14 +193,14 @@ public class CarrierResolver extends Handler { } /** - * This is triggered from SubscriptionInfoUpdater after sim state change. + * This is triggered from UiccController after sim state change. * The sequence of sim loading would be * 1. OnSubscriptionsChangedListener * 2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED * /ACTION_SIM_APPLICATION_STATE_CHANGED * 3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED * - * For SIM refresh either reset or init refresh type, SubscriptionInfoUpdater will re-trigger + * For SIM refresh either reset or init refresh type, UiccController will re-trigger * carrier identification with sim loaded state. Framework today silently handle single file * refresh type. * TODO: check fileId from single file refresh, if the refresh file is IMSI, gid1 or other @@ -548,12 +549,7 @@ public class CarrierResolver extends Handler { // subscriptioninfo db to make sure we have correct carrier id set. if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId()) && !isSimOverride) { // only persist carrier id to simInfo db when subId is valid. - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().setCarrierId(mPhone.getSubId(), - mCarrierId); - } else { - SubscriptionController.getInstance().setCarrierId(mCarrierId, mPhone.getSubId()); - } + SubscriptionManagerService.getInstance().setCarrierId(mPhone.getSubId(), mCarrierId); } } @@ -757,7 +753,8 @@ public class CarrierResolver extends Handler { // Ideally we should do full string match. However due to SIM manufacture issues // gid from some SIM might has garbage tail. private boolean gidMatch(String gidFromSim, String gid) { - return (gidFromSim != null) && gidFromSim.toLowerCase().startsWith(gid.toLowerCase()); + return (gidFromSim != null) && gidFromSim.toLowerCase(Locale.ROOT) + .startsWith(gid.toLowerCase(Locale.ROOT)); } private boolean carrierPrivilegeRulesMatch(List certsFromSubscription, diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java index bb5b6c0b27cab5fd6838433cb13d5ea4bb6372c9..6b44998db35a6b09bef9b2a0977bc679d85adcfe 100644 --- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java +++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java @@ -383,6 +383,7 @@ public class CarrierServiceStateTracker extends Handler { Notification.Builder builder = getNotificationBuilder(notificationType); // set some common attributes builder.setWhen(System.currentTimeMillis()) + .setShowWhen(true) .setAutoCancel(true) .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) .setColor(context.getResources().getColor( diff --git a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..82d44096a889ddf0541fb6450bcb7349ad2c887f --- /dev/null +++ b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java @@ -0,0 +1,534 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import android.annotation.NonNull; +import android.os.AsyncResult; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Message; +import android.telephony.CellBroadcastIdRange; +import android.telephony.SmsCbMessage; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; +import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +/** + * This class is to track the state to set cell broadcast config + */ + +public final class CellBroadcastConfigTracker extends StateMachine { + private static final boolean DBG = Build.IS_DEBUGGABLE; + + private static final int EVENT_REQUEST = 1; + private static final int EVENT_CONFIGURATION_DONE = 2; + private static final int EVENT_ACTIVATION_DONE = 3; + private static final int EVENT_RADIO_OFF = 4; + private static final int EVENT_SUBSCRIPTION_CHANGED = 5; + + private static final int SMS_CB_CODE_SCHEME_MIN = 0; + private static final int SMS_CB_CODE_SCHEME_MAX = 255; + + // Cache of current cell broadcast id ranges of 3gpp + private List mCbRanges3gpp = new CopyOnWriteArrayList<>(); + // Cache of current cell broadcast id ranges of 3gpp2 + private List mCbRanges3gpp2 = new CopyOnWriteArrayList<>(); + private Phone mPhone; + @VisibleForTesting + public int mSubId; + @VisibleForTesting + public final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener = + new SubscriptionManager.OnSubscriptionsChangedListener() { + @Override + public void onSubscriptionsChanged() { + sendMessage(EVENT_SUBSCRIPTION_CHANGED); + } + }; + + /** + * The class is to present the request to set cell broadcast id ranges + */ + private static class Request { + private final List mCbRangesRequest3gpp = + new CopyOnWriteArrayList<>(); + private final List mCbRangesRequest3gpp2 = + new CopyOnWriteArrayList<>(); + Consumer mCallback; + + Request(@NonNull List ranges, @NonNull Consumer callback) { + ranges.forEach(r -> { + if (r.getType() == SmsCbMessage.MESSAGE_FORMAT_3GPP) { + mCbRangesRequest3gpp.add(r); + } else { + mCbRangesRequest3gpp2.add(r); + } + }); + mCallback = callback; + } + + List get3gppRanges() { + return mCbRangesRequest3gpp; + } + + List get3gpp2Ranges() { + return mCbRangesRequest3gpp2; + } + + Consumer getCallback() { + return mCallback; + } + + @Override + public String toString() { + return "Request[mCbRangesRequest3gpp = " + mCbRangesRequest3gpp + ", " + + "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + ", " + + "mCallback = " + mCallback + "]"; + } + } + + /** + * The default state. + */ + private class DefaultState extends State { + @Override + public void enter() { + mPhone.registerForRadioOffOrNotAvailable(getHandler(), EVENT_RADIO_OFF, null); + mPhone.getContext().getSystemService(SubscriptionManager.class) + .addOnSubscriptionsChangedListener(new HandlerExecutor(getHandler()), + mSubChangedListener); + } + + @Override + public void exit() { + mPhone.unregisterForRadioOffOrNotAvailable(getHandler()); + mPhone.getContext().getSystemService(SubscriptionManager.class) + .removeOnSubscriptionsChangedListener(mSubChangedListener); + } + + @Override + public boolean processMessage(Message msg) { + boolean retVal = HANDLED; + if (DBG) { + logd("DefaultState message:" + msg.what); + } + switch (msg.what) { + case EVENT_RADIO_OFF: + resetConfig(); + break; + case EVENT_SUBSCRIPTION_CHANGED: + int subId = mPhone.getSubId(); + if (mSubId != subId) { + log("SubId changed from " + mSubId + " to " + subId); + mSubId = subId; + resetConfig(); + } + break; + default: + log("unexpected message!"); + break; + + } + + return retVal; + } + } + + private DefaultState mDefaultState = new DefaultState(); + + /* + * The idle state which does not have ongoing radio request. + */ + private class IdleState extends State { + @Override + public boolean processMessage(Message msg) { + boolean retVal = NOT_HANDLED; + if (DBG) { + logd("IdleState message:" + msg.what); + } + switch (msg.what) { + case EVENT_REQUEST: + Request request = (Request) msg.obj; + if (DBG) { + logd("IdleState handle EVENT_REQUEST with request:" + request); + } + if (!mCbRanges3gpp.equals(request.get3gppRanges())) { + // set gsm config if the config is changed + setGsmConfig(request.get3gppRanges(), request); + transitionTo(mGsmConfiguringState); + } else if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) { + // set cdma config directly if no gsm config change but cdma config is + // changed + setCdmaConfig(request.get3gpp2Ranges(), request); + transitionTo(mCdmaConfiguringState); + } else { + logd("Do nothing as the requested ranges are same as now"); + request.getCallback().accept( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS); + } + retVal = HANDLED; + break; + default: + break; + } + return retVal; + } + } + private IdleState mIdleState = new IdleState(); + + /* + * The state waiting for the result to set gsm config. + */ + private class GsmConfiguringState extends State { + @Override + public boolean processMessage(Message msg) { + boolean retVal = NOT_HANDLED; + if (DBG) { + logd("GsmConfiguringState message:" + msg.what); + } + switch (msg.what) { + case EVENT_REQUEST: + deferMessage(msg); + retVal = HANDLED; + break; + case EVENT_CONFIGURATION_DONE: + AsyncResult ar = (AsyncResult) msg.obj; + Request request = (Request) ar.userObj; + if (DBG) { + logd("GsmConfiguringState handle EVENT_CONFIGURATION_DONE with request:" + + request); + } + if (ar.exception == null) { + // set gsm activation and transit to gsm activating state + setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP, + !request.get3gppRanges().isEmpty(), request); + transitionTo(mGsmActivatingState); + } else { + logd("Failed to set gsm config"); + request.getCallback().accept( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG); + // transit to idle state on the failure case + transitionTo(mIdleState); + } + retVal = HANDLED; + break; + default: + break; + } + return retVal; + } + } + private GsmConfiguringState mGsmConfiguringState = new GsmConfiguringState(); + + /* + * The state waiting for the result to set gsm activation. + */ + private class GsmActivatingState extends State { + @Override + public boolean processMessage(Message msg) { + boolean retVal = NOT_HANDLED; + if (DBG) { + logd("GsmActivatingState message:" + msg.what); + } + switch (msg.what) { + case EVENT_REQUEST: + deferMessage(msg); + retVal = HANDLED; + break; + case EVENT_ACTIVATION_DONE: + AsyncResult ar = (AsyncResult) msg.obj; + Request request = (Request) ar.userObj; + if (DBG) { + logd("GsmActivatingState handle EVENT_ACTIVATION_DONE with request:" + + request); + } + if (ar.exception == null) { + mCbRanges3gpp = request.get3gppRanges(); + if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) { + // set cdma config and transit to cdma configuring state if the config + // is changed. + setCdmaConfig(request.get3gpp2Ranges(), request); + transitionTo(mCdmaConfiguringState); + } else { + logd("Done as no need to update ranges for 3gpp2"); + request.getCallback().accept( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS); + // transit to idle state if there is no cdma config change + transitionTo(mIdleState); + } + } else { + logd("Failed to set gsm activation"); + request.getCallback().accept( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION); + // transit to idle state on the failure case + transitionTo(mIdleState); + } + retVal = HANDLED; + break; + default: + break; + } + return retVal; + } + } + private GsmActivatingState mGsmActivatingState = new GsmActivatingState(); + + /* + * The state waiting for the result to set cdma config. + */ + private class CdmaConfiguringState extends State { + @Override + public boolean processMessage(Message msg) { + boolean retVal = NOT_HANDLED; + if (DBG) { + logd("CdmaConfiguringState message:" + msg.what); + } + switch (msg.what) { + case EVENT_REQUEST: + deferMessage(msg); + retVal = HANDLED; + break; + case EVENT_CONFIGURATION_DONE: + AsyncResult ar = (AsyncResult) msg.obj; + Request request = (Request) ar.userObj; + if (DBG) { + logd("CdmaConfiguringState handle EVENT_ACTIVATION_DONE with request:" + + request); + } + if (ar.exception == null) { + // set cdma activation and transit to cdma activating state + setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP2, + !request.get3gpp2Ranges().isEmpty(), request); + transitionTo(mCdmaActivatingState); + } else { + logd("Failed to set cdma config"); + request.getCallback().accept( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG); + // transit to idle state on the failure case + transitionTo(mIdleState); + } + retVal = HANDLED; + break; + default: + break; + } + return retVal; + } + } + private CdmaConfiguringState mCdmaConfiguringState = new CdmaConfiguringState(); + + /* + * The state waiting for the result to set cdma activation. + */ + private class CdmaActivatingState extends State { + @Override + public boolean processMessage(Message msg) { + boolean retVal = NOT_HANDLED; + if (DBG) { + logd("CdmaActivatingState message:" + msg.what); + } + switch (msg.what) { + case EVENT_REQUEST: + deferMessage(msg); + retVal = HANDLED; + break; + case EVENT_ACTIVATION_DONE: + AsyncResult ar = (AsyncResult) msg.obj; + Request request = (Request) ar.userObj; + if (DBG) { + logd("CdmaActivatingState handle EVENT_ACTIVATION_DONE with request:" + + request); + } + if (ar.exception == null) { + mCbRanges3gpp2 = request.get3gpp2Ranges(); + request.getCallback().accept( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS); + } else { + logd("Failed to set cdma activation"); + request.getCallback().accept( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION); + } + // transit to idle state anyway + transitionTo(mIdleState); + retVal = HANDLED; + break; + default: + break; + } + return retVal; + } + } + private CdmaActivatingState mCdmaActivatingState = new CdmaActivatingState(); + + private CellBroadcastConfigTracker(Phone phone) { + super("CellBroadcastConfigTracker-" + phone.getPhoneId()); + init(phone); + } + + private CellBroadcastConfigTracker(Phone phone, Handler handler) { + super("CellBroadcastConfigTracker-" + phone.getPhoneId(), handler); + init(phone); + } + + private void init(Phone phone) { + logd("init"); + mPhone = phone; + mSubId = mPhone.getSubId(); + + addState(mDefaultState); + addState(mIdleState, mDefaultState); + addState(mGsmConfiguringState, mDefaultState); + addState(mGsmActivatingState, mDefaultState); + addState(mCdmaConfiguringState, mDefaultState); + addState(mCdmaActivatingState, mDefaultState); + setInitialState(mIdleState); + } + + /** + * create a CellBroadcastConfigTracker instance for the phone + */ + public static CellBroadcastConfigTracker make(Phone phone, Handler handler, + boolean shouldStart) { + CellBroadcastConfigTracker tracker = handler == null + ? new CellBroadcastConfigTracker(phone) + : new CellBroadcastConfigTracker(phone, handler); + if (shouldStart) { + tracker.start(); + } + return tracker; + } + + /** + * Return current cell broadcast ranges. + */ + @NonNull public List getCellBroadcastIdRanges() { + List ranges = new ArrayList<>(); + ranges.addAll(mCbRanges3gpp); + ranges.addAll(mCbRanges3gpp2); + return ranges; + } + + /** + * Set reception of cell broadcast messages with the list of the given ranges. + */ + public void setCellBroadcastIdRanges( + @NonNull List ranges, @NonNull Consumer callback) { + if (DBG) { + logd("setCellBroadcastIdRanges with ranges:" + ranges); + } + ranges = mergeRangesAsNeeded(ranges); + sendMessage(EVENT_REQUEST, new Request(ranges, callback)); + } + + /** + * Merge the overlapped CellBroadcastIdRanges in the list as needed + * @param ranges the list of CellBroadcastIdRanges + * @return the list of CellBroadcastIdRanges without overlapping + * + * @throws IllegalArgumentException if there is conflict of the ranges. For instance, + * the channel is enabled in some range, but disable in others. + */ + @VisibleForTesting + public static @NonNull List mergeRangesAsNeeded( + @NonNull List ranges) throws IllegalArgumentException { + ranges.sort((r1, r2) -> r1.getType() != r2.getType() ? r1.getType() - r2.getType() + : (r1.getStartId() != r2.getStartId() ? r1.getStartId() - r2.getStartId() + : r2.getEndId() - r1.getEndId())); + final List newRanges = new ArrayList<>(); + ranges.forEach(r -> { + if (newRanges.isEmpty() || newRanges.get(newRanges.size() - 1).getType() != r.getType() + || newRanges.get(newRanges.size() - 1).getEndId() + 1 < r.getStartId() + || (newRanges.get(newRanges.size() - 1).getEndId() + 1 == r.getStartId() + && newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled())) { + newRanges.add(new CellBroadcastIdRange(r.getStartId(), r.getEndId(), + r.getType(), r.isEnabled())); + } else { + if (newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled()) { + throw new IllegalArgumentException("range conflict " + r); + } + if (r.getEndId() > newRanges.get(newRanges.size() - 1).getEndId()) { + CellBroadcastIdRange range = newRanges.get(newRanges.size() - 1); + newRanges.set(newRanges.size() - 1, new CellBroadcastIdRange( + range.getStartId(), r.getEndId(), range.getType(), range.isEnabled())); + } + } + }); + return newRanges; + } + + private void resetConfig() { + mCbRanges3gpp.clear(); + mCbRanges3gpp2.clear(); + } + + private void setGsmConfig(List ranges, Request request) { + if (DBG) { + logd("setGsmConfig with " + ranges); + } + + SmsBroadcastConfigInfo[] configs = new SmsBroadcastConfigInfo[ranges.size()]; + for (int i = 0; i < configs.length; i++) { + CellBroadcastIdRange r = ranges.get(i); + configs[i] = new SmsBroadcastConfigInfo(r.getStartId(), r.getEndId(), + SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, r.isEnabled()); + } + + Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request); + mPhone.mCi.setGsmBroadcastConfig(configs, response); + } + + private void setCdmaConfig(List ranges, Request request) { + if (DBG) { + logd("setCdmaConfig with " + ranges); + } + + CdmaSmsBroadcastConfigInfo[] configs = + new CdmaSmsBroadcastConfigInfo[ranges.size()]; + for (int i = 0; i < configs.length; i++) { + CellBroadcastIdRange r = ranges.get(i); + configs[i] = new CdmaSmsBroadcastConfigInfo( + r.getStartId(), r.getEndId(), 1, r.isEnabled()); + } + + Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request); + mPhone.mCi.setCdmaBroadcastConfig(configs, response); + } + + private void setActivation(int type, boolean activate, Request request) { + if (DBG) { + logd("setActivation(" + type + "." + activate + ')'); + } + + Message response = obtainMessage(EVENT_ACTIVATION_DONE, request); + + if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP) { + mPhone.mCi.setGsmBroadcastActivation(activate, response); + } else if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP2) { + mPhone.mCi.setCdmaBroadcastActivation(activate, response); + } + } +} diff --git a/src/java/com/android/internal/telephony/CellularNetworkService.java b/src/java/com/android/internal/telephony/CellularNetworkService.java index 4253905205dbd31d390307e9fdb455f290bb9cd6..9cbd7a6174a7ee2dd24d53117b06224ce1abae51 100644 --- a/src/java/com/android/internal/telephony/CellularNetworkService.java +++ b/src/java/com/android/internal/telephony/CellularNetworkService.java @@ -18,9 +18,9 @@ package com.android.internal.telephony; import android.annotation.NonNull; import android.annotation.Nullable; -import android.hardware.radio.V1_0.RegState; import android.hardware.radio.V1_4.DataRegStateResult.VopsInfo.hidl_discriminator; import android.hardware.radio.V1_6.RegStateResult.AccessTechnologySpecificInfo; +import android.hardware.radio.network.RegState; import android.os.AsyncResult; import android.os.Handler; import android.os.Looper; @@ -35,6 +35,7 @@ import android.telephony.CellIdentityLte; import android.telephony.CellIdentityNr; import android.telephony.CellIdentityTdscdma; import android.telephony.CellIdentityWcdma; +import android.telephony.DataSpecificRegistrationInfo; import android.telephony.LteVopsSupportInfo; import android.telephony.NetworkRegistrationInfo; import android.telephony.NetworkService; @@ -190,6 +191,8 @@ public class CellularNetworkService extends NetworkService { return NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN; case RegState.REG_ROAMING: return NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; + case RegState.REG_EM: + return NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY; default: return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; } @@ -201,6 +204,7 @@ public class CellularNetworkService extends NetworkService { case RegState.NOT_REG_MT_SEARCHING_OP_EM: case RegState.REG_DENIED_EM: case RegState.UNKNOWN_EM: + case RegState.REG_EM: return true; case RegState.NOT_REG_MT_NOT_SEARCHING_OP: case RegState.REG_HOME: @@ -504,7 +508,7 @@ public class CellularNetworkService extends NetworkService { && reasonForDenial == android.hardware.radio.network.RegistrationFailCause.NONE) { AnomalyReporter.reportAnomaly( - UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f20"), + UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f21"), "RIL Missing Reg Fail Reason", mPhone.getCarrierId()); } @@ -522,6 +526,8 @@ public class CellularNetworkService extends NetworkService { boolean isNrAvailable = false; boolean isDcNrRestricted = false; VopsSupportInfo vopsInfo = null; + int lteAttachResultType = 0; + int lteAttachExtraInfo = 0; android.hardware.radio.network.AccessTechnologySpecificInfo info = regResult.accessTechnologySpecificInfo; @@ -540,6 +546,8 @@ public class CellularNetworkService extends NetworkService { vopsInfo = convertHalLteVopsSupportInfo( info.getEutranInfo().lteVopsInfo.isVopsSupported, info.getEutranInfo().lteVopsInfo.isEmcBearerSupported); + lteAttachResultType = info.getEutranInfo().lteAttachResultType; + lteAttachExtraInfo = info.getEutranInfo().extraInfo; break; case android.hardware.radio.network.AccessTechnologySpecificInfo.ngranNrVopsInfo: vopsInfo = new NrVopsSupportInfo(info.getNgranNrVopsInfo().vopsSupported, @@ -565,10 +573,26 @@ public class CellularNetworkService extends NetworkService { loge("Unknown domain passed to CellularNetworkService= " + domain); // fall through case NetworkRegistrationInfo.DOMAIN_PS: - return new NetworkRegistrationInfo(domain, transportType, regState, networkType, - reasonForDenial, isEmergencyOnly, availableServices, cellIdentity, - rplmn, MAX_DATA_CALLS, isDcNrRestricted, isNrAvailable, isEndcAvailable, - vopsInfo); + return new NetworkRegistrationInfo.Builder() + .setDomain(domain) + .setTransportType(transportType) + .setRegistrationState(regState) + .setAccessNetworkTechnology(networkType) + .setRejectCause(reasonForDenial) + .setEmergencyOnly(isEmergencyOnly) + .setAvailableServices(availableServices) + .setCellIdentity(cellIdentity) + .setRegisteredPlmn(rplmn) + .setDataSpecificInfo( + new DataSpecificRegistrationInfo.Builder(MAX_DATA_CALLS) + .setDcNrRestricted(isDcNrRestricted) + .setNrAvailable(isNrAvailable) + .setEnDcAvailable(isEndcAvailable) + .setVopsSupportInfo(vopsInfo) + .setLteAttachResultType(lteAttachResultType) + .setLteAttachExtraInfo(lteAttachExtraInfo) + .build()) + .build(); } } @@ -594,7 +618,7 @@ public class CellularNetworkService extends NetworkService { && reasonForDenial == android.hardware.radio.network.RegistrationFailCause.NONE) { AnomalyReporter.reportAnomaly( - UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f20"), + UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f21"), "RIL Missing Reg Fail Reason", mPhone.getCarrierId()); } diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java index 72bb6a36f5ff61c83f884be2e38539f1c1f50358..e068c1cbff8e78769f725f1248b1eb6f725783c8 100644 --- a/src/java/com/android/internal/telephony/CommandException.java +++ b/src/java/com/android/internal/telephony/CommandException.java @@ -341,7 +341,6 @@ public class CommandException extends RuntimeException { return new CommandException(Error.RF_HARDWARE_ISSUE); case RILConstants.NO_RF_CALIBRATION_INFO: return new CommandException(Error.NO_RF_CALIBRATION_INFO); - default: Rlog.e("GSM", "Unrecognized RIL errno " + ril_errno); return new CommandException(Error.INVALID_RESPONSE); diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java index 27cedfeeb9cee59b6f39db6f56a9f978c1e50b9a..971e051c5ed479790b9145f9fa8a195f95315ff2 100644 --- a/src/java/com/android/internal/telephony/CommandsInterface.java +++ b/src/java/com/android/internal/telephony/CommandsInterface.java @@ -25,22 +25,31 @@ import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.WorkSource; +import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants.AccessNetworkType; +import android.telephony.BarringInfo; import android.telephony.CarrierRestrictionRules; import android.telephony.ClientRequestStats; +import android.telephony.DomainSelectionService; import android.telephony.ImsiEncryptionInfo; import android.telephony.NetworkScanRequest; import android.telephony.RadioAccessSpecifier; import android.telephony.SignalThresholdInfo; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.HalService; import android.telephony.data.DataCallResponse; import android.telephony.data.DataProfile; import android.telephony.data.NetworkSliceInfo; import android.telephony.data.TrafficDescriptor; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.RegistrationManager; +import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.stub.ImsRegistrationImplBase; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; +import com.android.internal.telephony.emergency.EmergencyConstants; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; +import com.android.internal.telephony.imsphone.ImsCallInfo; import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState; import com.android.internal.telephony.uicc.IccCardStatus; import com.android.internal.telephony.uicc.SimPhonebookRecord; @@ -124,6 +133,15 @@ public interface CommandsInterface { static final int CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM = 39; static final int CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM = 96; + /** IMS voice capability */ + int IMS_MMTEL_CAPABILITY_VOICE = 1 << 0; + /** IMS video capability */ + int IMS_MMTEL_CAPABILITY_VIDEO = 1 << 1; + /** IMS SMS capability */ + int IMS_MMTEL_CAPABILITY_SMS = 1 << 2; + /** IMS RCS capabilities */ + int IMS_RCS_CAPABILITIES = 1 << 3; + //***** Methods /** @@ -1772,6 +1790,17 @@ public interface CommandsInterface { */ public void getDeviceIdentity(Message response); + /** + * Request the device IMEI / IMEI type / IMEISV + * "response" is ImeiInfo object that contains + * [0] ImeiType Indicates whether IMEI is of primary or secondary type + * [1] IMEI if GSM subscription is available + * [2] IMEISV if GSM subscription is available + * + * @param response Message + */ + public void getImei(Message response); + /** * Request the device MDN / H_SID / H_NID / MIN. * "response" is const char ** @@ -2086,10 +2115,15 @@ public interface CommandsInterface { * * Input parameters equivalent to TS 27.007 AT+CCHC command. * + * Per spec SGP.22 V3.0, ES10 commands needs to be sent over command port of MEP-A. In order + * to close proper logical channel, should pass information about whether the logical channel + * was opened for sending ES10 commands or not. + * * @param channel Channel id. Id of the channel to be closed. + * @param isEs10 Whether the logical channel is opened to perform ES10 operations. * @param response Callback message. */ - public void iccCloseLogicalChannel(int channel, Message response); + public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response); /** * Exchange APDUs with the SIM on a logical channel. @@ -2105,11 +2139,12 @@ public interface CommandsInterface { * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU * is sent to the SIM. * @param data Data to be sent with the APDU. + * @param isEs10Command whether APDU command is an ES10 command or a regular APDU * @param response Callback message. response.obj.userObj will be * an IccIoResult on success. */ - public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, - int p1, int p2, int p3, String data, Message response); + void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, + int p1, int p2, int p3, String data, boolean isEs10Command, Message response); /** * Exchange APDUs with the SIM on a basic channel. @@ -2186,11 +2221,21 @@ public interface CommandsInterface { /** * @return the radio hal version + * @deprecated use {@link #getHalVersion(int)} */ + @Deprecated default HalVersion getHalVersion() { return HalVersion.UNKNOWN; } + /** + * @param service indicate the service id to query. + * @return the hal version of a specific service + */ + default HalVersion getHalVersion(@HalService int service) { + return HalVersion.UNKNOWN; + } + /** * Sets user selected subscription at Modem. * @@ -2618,6 +2663,15 @@ public interface CommandsInterface { */ default void getBarringInfo(Message result) {}; + /** + * Returns the last barring information received. + * + * @return the last barring information. + */ + default @Nullable BarringInfo getLastBarringInfo() { + return null; + }; + /** * Allocates a pdu session id * @@ -2746,6 +2800,54 @@ public interface CommandsInterface { */ public void unregisterForSimPhonebookRecordsReceived(Handler h); + /** + * Registers for notifications of connection setup failure. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForConnectionSetupFailure(Handler h, int what, Object obj) {} + + /** + * Unregisters for notifications of connection setup failure. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForConnectionSetupFailure(Handler h) {} + + /** + * Registers for notifications when ANBR is received form the network. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForNotifyAnbr(Handler h, int what, Object obj) {} + + /** + * Unregisters for notifications when ANBR is received form the network. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForNotifyAnbr(Handler h) {} + + /** + * Registers for IMS deregistration trigger from modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForTriggerImsDeregistration(Handler h, int what, Object obj) {} + + /** + * Unregisters for IMS deregistration trigger from modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForTriggerImsDeregistration(Handler h) {} + /** * Set the UE's usage setting. * @@ -2761,4 +2863,417 @@ public interface CommandsInterface { * @param result Callback message containing the usage setting (or a failure status). */ default void getUsageSetting(Message result) {} + + /** + * Sets the emergency mode. + * + * @param emcMode Defines the radio emergency mode type. + * @param result Callback message containing the success or failure status. + */ + default void setEmergencyMode(@EmergencyConstants.EmergencyMode int emcMode, + @Nullable Message result) {} + + /** + * Triggers an emergency network scan. + * + * @param accessNetwork Contains the list of access network types to be prioritized + * during emergency scan. The 1st entry has the highest priority. + * @param scanType Indicates the type of scans to be performed i.e. limited scan, + * full service scan or both. + * @param result Callback message containing the success or failure status. + */ + default void triggerEmergencyNetworkScan( + @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork, + @DomainSelectionService.EmergencyScanType int scanType, @Nullable Message result) {} + + /** + * Cancels ongoing emergency network scan. + * + * @param resetScan Indicates how the next {@link #triggerEmergencyNetworkScan} should work. + * If {@code true}, then the modem shall start the new scan from the beginning, + * otherwise the modem shall resume from the last search. + * @param result Callback message containing the success or failure status. + */ + default void cancelEmergencyNetworkScan(boolean resetScan, @Nullable Message result) {} + + /** + * Exits ongoing emergency mode. + * + * @param result Callback message containing the success or failure status. + */ + default void exitEmergencyMode(@Nullable Message result) {} + + /** + * Registers for emergency network scan result. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForEmergencyNetworkScan(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for emergency network scan result. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForEmergencyNetworkScan(@NonNull Handler h) {} + + /** + * Provides a list of SRVCC call information to radio + * + * @param srvccConnections the list of connections. + */ + default void setSrvccCallInfo(SrvccConnection[] srvccConnections, Message result) {} + + /** + * Updates the IMS registration information to the radio. + * + * @param state The current IMS registration state. + * @param imsRadioTech The type of underlying radio access network used. + * @param suggestedAction The suggested action for the radio to perform. + * @param capabilities IMS capabilities such as VOICE, VIDEO and SMS. + */ + default void updateImsRegistrationInfo(int state, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech, + @RegistrationManager.SuggestedAction int suggestedAction, + int capabilities, Message result) {} + + /** + * Notifies the NAS and RRC layers of the radio the type of upcoming IMS traffic. + * + * @param token A nonce to identify the request. + * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc. + * @param accessNetworkType The type of underlying radio access network used. + * @param trafficDirection Indicates whether traffic is originated by mobile originated or + * mobile terminated use case eg. MO/MT call/SMS etc. + */ + default void startImsTraffic(int token, + @MmTelFeature.ImsTrafficType int trafficType, + @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType, + @MmTelFeature.ImsTrafficDirection int trafficDirection, + Message result) {} + + /** + * Notifies IMS traffic has been stopped. + * + * @param token The token assigned by startImsTraffic. + */ + default void stopImsTraffic(int token, Message result) {} + + /** + * Triggers the UE initiated EPS fallback procedure. + * + * @param reason Specifies the reason for EPS fallback. + */ + default void triggerEpsFallback(int reason, Message result) {} + + /** + * Triggers radio to send ANBRQ message to the network. + * + * @param mediaType Media type is used to identify media stream such as audio or video. + * @param direction Direction of this packet stream (e.g. uplink or downlink). + * @param bitsPerSecond The bit rate requested by the opponent UE. + * @param result Callback message to receive the result. + */ + default void sendAnbrQuery(int mediaType, int direction, int bitsPerSecond, Message result) {} + + /** + * Set the UE's ability to accept/reject null ciphered and/or null integrity-protected + * connections. + * + * @param enabled true to allow null ciphered and/or null integrity-protected connections, + * false to disallow. + * @param result Callback message containing the success or failure status. + */ + default void setNullCipherAndIntegrityEnabled(boolean enabled, Message result) {} + + /** + * Check whether null ciphering and/or null integrity-protected connections are allowed. + * + * @param result Callback message containing the success or failure status. + */ + default void isNullCipherAndIntegrityEnabled(Message result) {} + + /** + * Notifies the IMS call status to the modem. + * + * @param imsCallInfo The list of {@link ImsCallInfo}. + * @param result A callback to receive the response. + */ + default void updateImsCallStatus(@NonNull List imsCallInfo, Message result) {} + + /** + * Enables or disables N1 mode (access to 5G core network) in accordance with + * 3GPP TS 24.501 4.9. + * @param enable {@code true} to enable N1 mode, {@code false} to disable N1 mode. + * @param result Callback message to receive the result. + */ + default void setN1ModeEnabled(boolean enable, Message result) {} + + /** + * Check whether N1 mode (access to 5G core network) is enabled or not. + * @param result Callback message to receive the result. + */ + default void isN1ModeEnabled(Message result) {} + + /** + * Get feature capabilities supported by satellite. + * + * @param result Message that will be sent back to the requester + */ + default void getSatelliteCapabilities(Message result) {} + + /** + * Turn satellite modem on/off. + * + * @param result Message that will be sent back to the requester + * @param on {@code true} for turning on. + * {@code false} for turning off. + */ + default void setSatellitePower(Message result, boolean on) {} + + /** + * Get satellite modem state. + * + * @param result Message that will be sent back to the requester + */ + default void getSatellitePowerState(Message result) {} + + /** + * Get satellite provision state. + * + * @param result Message that will be sent back to the requester + */ + default void getSatelliteProvisionState(Message result) {} + + /** + * Check whether satellite modem is supported by the device. + * + * @param result Message that will be sent back to the requester + */ + default void isSatelliteSupported(Message result) {} + + /** + * Provision the subscription with a satellite provider. This is needed to register the + * subscription if the provider allows dynamic registration. + * + * @param result Message that will be sent back to the requester. + * @param imei IMEI of the SIM associated with the satellite modem. + * @param msisdn MSISDN of the SIM associated with the satellite modem. + * @param imsi IMSI of the SIM associated with the satellite modem. + * @param features List of features to be provisioned. + */ + default void provisionSatelliteService( + Message result, String imei, String msisdn, String imsi, int[] features) {} + + /** + * Add contacts that are allowed to be used for satellite communication. This is applicable for + * incoming messages as well. + * + * @param result Message that will be sent back to the requester. + * @param contacts List of allowed contacts to be added. + */ + default void addAllowedSatelliteContacts(Message result, String[] contacts) {} + + /** + * Remove contacts that are allowed to be used for satellite communication. This is applicable + * for incoming messages as well. + * + * @param result Message that will be sent back to the requester. + * @param contacts List of allowed contacts to be removed. + */ + default void removeAllowedSatelliteContacts(Message result, String[] contacts) {} + + /** + * Send text messages. + * + * @param result Message that will be sent back to the requester. + * @param messages List of messages in text format to be sent. + * @param destination The recipient of the message. + * @param latitude The current latitude of the device. + * @param longitude The current longitude of the device. The location (i.e., latitude and + * longitude) of the device will be filled for emergency messages. + */ + default void sendSatelliteMessages(Message result, String[] messages, String destination, + double latitude, double longitude) {} + + /** + * Get pending messages. + * + * @param result Message that will be sent back to the requester. + */ + default void getPendingSatelliteMessages(Message result) {} + + /** + * Get current satellite registration mode. + * + * @param result Message that will be sent back to the requester. + */ + default void getSatelliteMode(Message result) {} + + /** + * Set the filter for what type of indication framework want to receive from modem. + * + * @param result Message that will be sent back to the requester. + * @param filterBitmask The filter bitmask identifying what type of indication Telephony + * framework wants to receive from modem. + */ + default void setSatelliteIndicationFilter(Message result, int filterBitmask) {} + + /** + * User started pointing to the satellite. Modem should continue to update the ponting input + * as user moves device. + * + * @param result Message that will be sent back to the requester. + */ + default void startSendingSatellitePointingInfo(Message result) {} + + /** + * Stop sending satellite pointing info to the framework. + * + * @param result Message that will be sent back to the requester. + */ + default void stopSendingSatellitePointingInfo(Message result) {} + + /** + * Get max number of characters per text message. + * + * @param result Message that will be sent back to the requester. + */ + default void getMaxCharactersPerSatelliteTextMessage(Message result) {} + + /** + * Get whether satellite communication is allowed for the current location. + * + * @param result Message that will be sent back to the requester. + */ + default void isSatelliteCommunicationAllowedForCurrentLocation(Message result) {} + + /** + * Get the time after which the satellite will be visible. + * + * @param result Message that will be sent back to the requester. + */ + default void getTimeForNextSatelliteVisibility(Message result) {} + + /** + * Registers for pending message count from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForPendingSatelliteMessageCount(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for pending message count from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForPendingSatelliteMessageCount(@NonNull Handler h) {} + + /** + * Registers for new messages from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForNewSatelliteMessages(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for new messages from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForNewSatelliteMessages(@NonNull Handler h) {} + + /** + * Registers for messages transfer complete from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForSatelliteMessagesTransferComplete(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for messages transfer complete from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForSatelliteMessagesTransferComplete(@NonNull Handler h) {} + + /** + * Registers for pointing info changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForSatellitePointingInfoChanged(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for pointing info changed from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForSatellitePointingInfoChanged(@NonNull Handler h) {} + + /** + * Registers for mode changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForSatelliteModeChanged(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for mode changed from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForSatelliteModeChanged(@NonNull Handler h) {} + + /** + * Registers for radio technology changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForSatelliteRadioTechnologyChanged(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for radio technology changed from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForSatelliteRadioTechnologyChanged(@NonNull Handler h) {} + + /** + * Registers for provision state changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + default void registerForSatelliteProvisionStateChanged(@NonNull Handler h, + int what, @Nullable Object obj) {} + + /** + * Unregisters for provision state changed from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + default void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) {} } diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java index c60e5df900d82e8b8f1ad85e058cb1770abdbfab..68fd6abcdebd17b57d66229310fc22deed167996 100644 --- a/src/java/com/android/internal/telephony/Connection.java +++ b/src/java/com/android/internal/telephony/Connection.java @@ -27,6 +27,8 @@ import android.telephony.ServiceState; import android.telephony.ServiceState.RilRadioTechnology; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RtpHeaderExtension; +import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.feature.MmTelFeature.ImsAudioHandler; import android.util.Log; import com.android.ims.internal.ConferenceParticipant; @@ -139,6 +141,13 @@ public abstract class Connection { * @param extensionData The extension data. */ public void onReceivedRtpHeaderExtensions(@NonNull Set extensionData); + + /** + * Indicates that the audio handler for this connection is changed. + * + * @param imsAudioHandler {@link MmTelFeature#ImsAudioHandler}. + */ + void onAudioModeIsVoipChanged(@ImsAudioHandler int imsAudioHandler); } /** @@ -194,6 +203,8 @@ public abstract class Connection { public void onReceivedDtmfDigit(char digit) {} @Override public void onReceivedRtpHeaderExtensions(@NonNull Set extensionData) {} + @Override + public void onAudioModeIsVoipChanged(@ImsAudioHandler int imsAudioHandler) {} } public static final int AUDIO_QUALITY_STANDARD = 1; @@ -327,6 +338,41 @@ public abstract class Connection { /* Instance Methods */ + /** + * PhoneFactory Dependencies for testing. + */ + @VisibleForTesting + public interface PhoneFactoryProxy { + Phone getPhone(int index); + Phone getDefaultPhone(); + Phone[] getPhones(); + } + + private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() { + @Override + public Phone getPhone(int index) { + return PhoneFactory.getPhone(index); + } + + @Override + public Phone getDefaultPhone() { + return PhoneFactory.getDefaultPhone(); + } + + @Override + public Phone[] getPhones() { + return PhoneFactory.getPhones(); + } + }; + + /** + * Overrides PhoneFactory dependencies for testing. + */ + @VisibleForTesting + public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) { + mPhoneFactoryProxy = proxy; + } + /** * @return The telecom internal call ID associated with this connection. Only to be used for * debugging purposes. @@ -590,14 +636,35 @@ public abstract class Connection { */ public void setEmergencyCallInfo(CallTracker ct) { if (ct != null) { - Phone phone = ct.getPhone(); - if (phone != null) { - EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker(); + Phone currentPhone = ct.getPhone(); + if (currentPhone != null) { + EmergencyNumberTracker tracker = currentPhone.getEmergencyNumberTracker(); if (tracker != null) { EmergencyNumber num = tracker.getEmergencyNumber(mAddress); + Phone[] allPhones = mPhoneFactoryProxy.getPhones(); if (num != null) { mIsEmergencyCall = true; mEmergencyNumberInfo = num; + } else if (allPhones.length > 1) { + // If there are multiple active SIMs, check all instances: + boolean found = false; + for (Phone phone : allPhones) { + // If the current iteration was already checked, skip: + if (phone.getPhoneId() == currentPhone.getPhoneId()){ + continue; + } + num = phone.getEmergencyNumberTracker() + .getEmergencyNumber(mAddress); + if (num != null){ + found = true; + mIsEmergencyCall = true; + mEmergencyNumberInfo = num; + break; + } + } + if (!found){ + Rlog.e(TAG, "setEmergencyCallInfo: emergency number is null"); + } } else { Rlog.e(TAG, "setEmergencyCallInfo: emergency number is null"); } @@ -1518,6 +1585,25 @@ public abstract class Connection { } } + /** + * Called to report audio mode changed for Voip. + * @param imsAudioHandler the received value to handle the audio for this IMS call. + */ + public void onAudioModeIsVoipChanged(@ImsAudioHandler int imsAudioHandler) { + Rlog.i(TAG, "onAudioModeIsVoipChanged: conn imsAudioHandler " + imsAudioHandler); + + boolean isVoip = imsAudioHandler == MmTelFeature.AUDIO_HANDLER_ANDROID; + if (isVoip == mAudioModeIsVoip) return; + mAudioModeIsVoip = isVoip; + + Rlog.i(TAG, "onAudioModeIsVoipChanged: isVoip: " + isVoip + + "mAudioModeIsVoip:" + mAudioModeIsVoip); + + for (Listener l : mListeners) { + l.onAudioModeIsVoipChanged(imsAudioHandler); + } + } + /** * Called to report RTP header extensions received from the network. * @param extensionData the received extension data. diff --git a/src/java/com/android/internal/telephony/DataIndication.java b/src/java/com/android/internal/telephony/DataIndication.java index 346795589a5c0dc3f9ceaab3d1ac2886ad52cb76..205c4d8b2c5cd70d53b23af7c14e801aa091fe47 100644 --- a/src/java/com/android/internal/telephony/DataIndication.java +++ b/src/java/com/android/internal/telephony/DataIndication.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_DATA; + import static com.android.internal.telephony.RILConstants.RIL_UNSOL_DATA_CALL_LIST_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_KEEPALIVE_STATUS; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PCO_DATA; @@ -53,7 +55,7 @@ public class DataIndication extends IRadioDataIndication.Stub { */ public void dataCallListChanged(int indicationType, android.hardware.radio.data.SetupDataCallResult[] dcList) { - mRil.processIndication(RIL.DATA_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_DATA, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList); ArrayList response = RILUtils.convertHalDataCallResultList(dcList); @@ -68,7 +70,7 @@ public class DataIndication extends IRadioDataIndication.Stub { */ public void keepaliveStatus(int indicationType, android.hardware.radio.data.KeepaliveStatus halStatus) { - mRil.processIndication(RIL.DATA_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_DATA, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogRet( @@ -87,7 +89,7 @@ public class DataIndication extends IRadioDataIndication.Stub { * @param pco New PcoData */ public void pcoData(int indicationType, android.hardware.radio.data.PcoDataInfo pco) { - mRil.processIndication(RIL.DATA_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_DATA, indicationType); PcoData response = new PcoData(pco.cid, pco.bearerProto, pco.pcoId, pco.contents); @@ -104,7 +106,7 @@ public class DataIndication extends IRadioDataIndication.Stub { */ public void unthrottleApn(int indicationType, android.hardware.radio.data.DataProfileInfo dpi) throws RemoteException { - mRil.processIndication(RIL.DATA_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_DATA, indicationType); DataProfile response = RILUtils.convertToDataProfile(dpi); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, response); @@ -120,7 +122,7 @@ public class DataIndication extends IRadioDataIndication.Stub { */ public void slicingConfigChanged(int indicationType, android.hardware.radio.data.SlicingConfig slicingConfig) throws RemoteException { - mRil.processIndication(RIL.DATA_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_DATA, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogRet(RIL_UNSOL_SLICING_CONFIG_CHANGED, slicingConfig); } diff --git a/src/java/com/android/internal/telephony/DataResponse.java b/src/java/com/android/internal/telephony/DataResponse.java index 7cfe13b22980b8a6ec2bbe1893303b94b3ec9db6..bef1da73175efc57d0412777e8419c36131c8ebf 100644 --- a/src/java/com/android/internal/telephony/DataResponse.java +++ b/src/java/com/android/internal/telephony/DataResponse.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_DATA; + import android.hardware.radio.RadioError; import android.hardware.radio.RadioResponseInfo; import android.hardware.radio.data.IRadioDataResponse; @@ -51,7 +53,7 @@ public class DataResponse extends IRadioDataResponse.Stub { * @param id The pdu session id allocated */ public void allocatePduSessionIdResponse(RadioResponseInfo responseInfo, int id) { - RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { RadioResponse.sendMessageResponse(rr.mResult, id); @@ -64,14 +66,14 @@ public class DataResponse extends IRadioDataResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void cancelHandoverResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void deactivateDataCallResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** @@ -80,7 +82,7 @@ public class DataResponse extends IRadioDataResponse.Stub { */ public void getDataCallListResponse(RadioResponseInfo responseInfo, android.hardware.radio.data.SetupDataCallResult[] dataCallResultList) { - RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo); if (rr != null) { ArrayList response = @@ -98,7 +100,7 @@ public class DataResponse extends IRadioDataResponse.Stub { */ public void getSlicingConfigResponse(RadioResponseInfo responseInfo, android.hardware.radio.data.SlicingConfig slicingConfig) { - RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo); if (rr != null) { NetworkSlicingConfig ret = RILUtils.convertHalSlicingConfig(slicingConfig); @@ -113,35 +115,35 @@ public class DataResponse extends IRadioDataResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void releasePduSessionIdResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setDataAllowedResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setDataProfileResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setDataThrottlingResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setInitialAttachApnResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** @@ -150,7 +152,7 @@ public class DataResponse extends IRadioDataResponse.Stub { */ public void setupDataCallResponse(RadioResponseInfo responseInfo, android.hardware.radio.data.SetupDataCallResult setupDataCallResult) { - RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo); if (rr != null) { DataCallResponse response = RILUtils.convertHalDataCallResult(setupDataCallResult); @@ -165,7 +167,7 @@ public class DataResponse extends IRadioDataResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void startHandoverResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo); } /** @@ -175,7 +177,7 @@ public class DataResponse extends IRadioDataResponse.Stub { public void startKeepaliveResponse(RadioResponseInfo responseInfo, android.hardware.radio.data.KeepaliveStatus keepaliveStatus) { - RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo); if (rr == null) return; KeepaliveStatus ret = null; @@ -214,7 +216,7 @@ public class DataResponse extends IRadioDataResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void stopKeepaliveResponse(RadioResponseInfo responseInfo) { - RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo); if (rr == null) return; try { diff --git a/src/java/com/android/internal/telephony/DebugService.java b/src/java/com/android/internal/telephony/DebugService.java index 5cc730cc4fd07d9fc17ac2a4183de4335e542594..3341577d04b181b8ea683aa24f02ffb81e36293f 100644 --- a/src/java/com/android/internal/telephony/DebugService.java +++ b/src/java/com/android/internal/telephony/DebugService.java @@ -52,13 +52,15 @@ public class DebugService { TelephonyMetrics.getInstance().dump(fd, pw, args); return; case "--saveatoms": - log("Saving atoms.."); - PhoneFactory.getMetricsCollector().getAtomsStorage().flushAtoms(); + if (Build.IS_DEBUGGABLE) { + log("Saving atoms.."); + PhoneFactory.getMetricsCollector().flushAtomsStorage(); + } return; case "--clearatoms": if (Build.IS_DEBUGGABLE) { log("Clearing atoms.."); - PhoneFactory.getMetricsCollector().getAtomsStorage().clearAtoms(); + PhoneFactory.getMetricsCollector().clearAtomsStorage(); } return; } diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java index e4aff4c9118b3afd6a6ba8865ec1b92e558c7187..e5a5c8fd88e86909e89293596065b5b6a52ed6c1 100644 --- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java +++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java @@ -18,6 +18,7 @@ package com.android.internal.telephony; import android.annotation.NonNull; import android.content.Context; +import android.telephony.Annotation; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SrvccState; import android.telephony.BarringInfo; @@ -32,9 +33,13 @@ import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager.DataEnabledReason; +import android.telephony.TelephonyManager.EmergencyCallbackModeStopReason; +import android.telephony.TelephonyManager.EmergencyCallbackModeType; import android.telephony.TelephonyRegistryManager; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; import com.android.telephony.Rlog; @@ -142,15 +147,28 @@ public class DefaultPhoneNotifier implements PhoneNotifier { mTelephonyRegistryMgr.notifyCellInfoChanged(subId, cellInfo); } - public void notifyPreciseCallState(Phone sender) { + /** + * Notify precise call state of foreground, background and ringing call states. + * + * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for + * ringing, foreground & background calls. + * @param imsCallServiceTypes Array of IMS call service type for ringing, foreground & + * background calls. + * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls. + */ + public void notifyPreciseCallState(Phone sender, String[] imsCallIds, + @Annotation.ImsCallServiceType int[] imsCallServiceTypes, + @Annotation.ImsCallType int[] imsCallTypes) { Call ringingCall = sender.getRingingCall(); Call foregroundCall = sender.getForegroundCall(); Call backgroundCall = sender.getBackgroundCall(); + if (ringingCall != null && foregroundCall != null && backgroundCall != null) { - mTelephonyRegistryMgr.notifyPreciseCallState(sender.getPhoneId(), sender.getSubId(), - convertPreciseCallState(ringingCall.getState()), + int[] callStates = {convertPreciseCallState(ringingCall.getState()), convertPreciseCallState(foregroundCall.getState()), - convertPreciseCallState(backgroundCall.getState())); + convertPreciseCallState(backgroundCall.getState())}; + mTelephonyRegistryMgr.notifyPreciseCallState(sender.getPhoneId(), sender.getSubId(), + callStates, imsCallIds, imsCallServiceTypes, imsCallTypes); } } @@ -222,6 +240,12 @@ public class DefaultPhoneNotifier implements PhoneNotifier { callQuality, callNetworkType); } + @Override + public void notifyMediaQualityStatusChanged(Phone sender, MediaQualityStatus status) { + mTelephonyRegistryMgr.notifyMediaQualityStatusChanged( + sender.getPhoneId(), sender.getSubId(), status); + } + @Override public void notifyRegistrationFailed(Phone sender, @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) { @@ -262,6 +286,18 @@ public class DefaultPhoneNotifier implements PhoneNotifier { sender.getSubId(), linkCapacityEstimateList); } + @Override + public void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type) { + mTelephonyRegistryMgr.notifyCallBackModeStarted(sender.getPhoneId(), + sender.getSubId(), type); + } + + @Override + public void notifyCallbackModeStopped(Phone sender, @EmergencyCallbackModeType int type, + @EmergencyCallbackModeStopReason int reason) { + mTelephonyRegistryMgr.notifyCallbackModeStopped(sender.getPhoneId(), + sender.getSubId(), type, reason); + } /** * Convert the {@link Call.State} enum into the PreciseCallState.PRECISE_CALL_STATE_* constants * for the public API. diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java index 3d63a29f5e493f94c65c40510613868b336f24f6..ecc6208192f4dcc89d7d96177b9e1bafc607ff04 100644 --- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java +++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java @@ -20,6 +20,7 @@ import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; import static android.hardware.radio.V1_0.DeviceStateType.CHARGING_STATE; import static android.hardware.radio.V1_0.DeviceStateType.LOW_DATA_EXPECTED; import static android.hardware.radio.V1_0.DeviceStateType.POWER_SAVE_MODE; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; import android.app.UiModeManager; import android.content.BroadcastReceiver; @@ -669,7 +670,7 @@ public class DeviceStateMonitor extends Handler { LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.EUTRAN); mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS, LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.CDMA2000); - if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { + if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS, LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.NGRAN); } diff --git a/src/java/com/android/internal/telephony/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java index f1e2608fabf50726efe732aad367fd0ee1b04ab2..567331d4597a807b895234f0613b2b507edd55dd 100644 --- a/src/java/com/android/internal/telephony/DisplayInfoController.java +++ b/src/java/com/android/internal/telephony/DisplayInfoController.java @@ -21,9 +21,7 @@ import android.os.Handler; import android.os.Message; import android.os.Registrant; import android.os.RegistrantList; -import android.telephony.AccessNetworkConstants; import android.telephony.AnomalyReporter; -import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; @@ -47,8 +45,6 @@ import javax.sip.InvalidArgumentException; * TelephonyDisplayInfo via {@link #getTelephonyDisplayInfo}. */ public class DisplayInfoController extends Handler { - private static final String TAG = "DisplayInfoController"; - private final String mLogTag; private final LocalLog mLocalLog = new LocalLog(128); @@ -106,12 +102,10 @@ public class DisplayInfoController extends Handler { * NetworkTypeController. */ public void updateTelephonyDisplayInfo() { - NetworkRegistrationInfo nri = mPhone.getServiceState().getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - int dataNetworkType = nri == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN - : nri.getAccessNetworkTechnology(); - TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(dataNetworkType, - mNetworkTypeController.getOverrideNetworkType(), mServiceState.getRoaming()); + TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo( + mNetworkTypeController.getDataNetworkType(), + mNetworkTypeController.getOverrideNetworkType(), + mServiceState.getRoaming()); if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) { logl("TelephonyDisplayInfo changed from " + mTelephonyDisplayInfo + " to " + newDisplayInfo); @@ -149,7 +143,7 @@ public class DisplayInfoController extends Handler { } } catch (InvalidArgumentException e) { logel(e.getMessage()); - AnomalyReporter.reportAnomaly(UUID.fromString("3aa92a2c-94ed-46a0-a744-d6b1dfec2a55"), + AnomalyReporter.reportAnomaly(UUID.fromString("3aa92a2c-94ed-46a0-a744-d6b1dfec2a56"), e.getMessage(), mPhone.getCarrierId()); } } diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java old mode 100755 new mode 100644 index 7738b44b5c9172708864fcc239535bd5be523bab..d76ee199fc6213dd5946ed63263d064a388f885f --- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java +++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java @@ -45,6 +45,8 @@ import android.util.EventLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.PhoneInternalInterface.DialArgs; import com.android.internal.telephony.cdma.CdmaCallWaitingNotification; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; +import com.android.internal.telephony.emergency.EmergencyStateTracker; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.telephony.Rlog; @@ -480,16 +482,29 @@ public class GsmCdmaCallTracker extends CallTracker { disableDataCallInEmergencyCall(dialString); // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit. - if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) { + if (!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) { mCi.dial(mPendingMO.getAddress(), mPendingMO.isEmergencyCall(), mPendingMO.getEmergencyNumberInfo(), - mPendingMO.hasKnownUserIntentEmergency(), - clirMode, obtainCompleteMessage()); + mPendingMO.hasKnownUserIntentEmergency(), clirMode, + obtainCompleteMessage()); + } else if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + mPendingCallInEcm = true; + final int finalClirMode = clirMode; + Runnable onComplete = new Runnable() { + @Override + public void run() { + mCi.dial(mPendingMO.getAddress(), mPendingMO.isEmergencyCall(), + mPendingMO.getEmergencyNumberInfo(), + mPendingMO.hasKnownUserIntentEmergency(), finalClirMode, + obtainCompleteMessage()); + } + }; + EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete); } else { mPhone.exitEmergencyCallbackMode(); - mPhone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null); - mPendingCallClirMode=clirMode; - mPendingCallInEcm=true; + mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null); + mPendingCallClirMode = clirMode; + mPendingCallInEcm = true; } } @@ -959,6 +974,8 @@ public class GsmCdmaCallTracker extends CallTracker { } else { newUnknownConnectionCdma = mConnections[i]; } + } else if (hangupWaitingCallSilently(i)) { + return; } } } @@ -1010,6 +1027,9 @@ public class GsmCdmaCallTracker extends CallTracker { if (mConnections[i].getCall() == mRingingCall) { newRinging = mConnections[i]; + if (hangupWaitingCallSilently(i)) { + return; + } } // else something strange happened hasNonHangupStateChanged = true; } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */ @@ -1819,6 +1839,10 @@ public class GsmCdmaCallTracker extends CallTracker { } private boolean isEmcRetryCause(int causeCode) { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + log("isEmcRetryCause AP based domain selection ignores the cause"); + return false; + } if (causeCode == CallFailCause.EMC_REDIAL_ON_IMS || causeCode == CallFailCause.EMC_REDIAL_ON_VOWIFI) { return true; @@ -1898,4 +1922,22 @@ public class GsmCdmaCallTracker extends CallTracker { public void cleanupCalls() { pollCallsWhenSafe(); } + + private boolean hangupWaitingCallSilently(int index) { + if (index < 0 || index >= mConnections.length) return false; + + GsmCdmaConnection newRinging = mConnections[index]; + if (newRinging == null) return false; + + if ((mPhone.getTerminalBasedCallWaitingState(true) + == CallWaitingController.TERMINAL_BASED_NOT_ACTIVATED) + && (newRinging.getState() == Call.State.WAITING)) { + Rlog.d(LOG_TAG, "hangupWaitingCallSilently"); + newRinging.dispose(); + mConnections[index] = null; + mCi.hangupWaitingOrBackground(obtainCompleteMessage()); + return true; + } + return false; + } } diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java index 8850f9334b8b10728bb8bdf88f84f22f98b7ec64..5eae06112ce32315903d11b89691e3764359afbd 100644 --- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java +++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java @@ -16,6 +16,11 @@ package com.android.internal.telephony; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS_PS; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_UNKNOWN; + import static com.android.internal.telephony.CommandException.Error.GENERIC_FAILURE; import static com.android.internal.telephony.CommandException.Error.SIM_BUSY; import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE; @@ -32,6 +37,7 @@ import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOI import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.ContentValues; @@ -40,6 +46,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.SQLException; +import android.hardware.radio.modem.ImeiInfo; import android.net.Uri; import android.os.AsyncResult; import android.os.Build; @@ -57,6 +64,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.WorkSource; import android.preference.PreferenceManager; +import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Telephony; import android.sysprop.TelephonyProperties; @@ -64,10 +72,13 @@ import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telecom.VideoProfile; +import android.telephony.AccessNetworkConstants.TransportType; import android.telephony.Annotation.DataActivityType; import android.telephony.Annotation.RadioPowerState; +import android.telephony.AnomalyReporter; import android.telephony.BarringInfo; import android.telephony.CarrierConfigManager; +import android.telephony.CellBroadcastIdRange; import android.telephony.CellIdentity; import android.telephony.ImsiEncryptionInfo; import android.telephony.LinkCapacityEstimate; @@ -81,6 +92,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccAccessRule; import android.telephony.UssdResponse; +import android.telephony.ims.ImsCallProfile; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -92,7 +104,9 @@ 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.data.LinkBandwidthEstimator; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; import com.android.internal.telephony.emergency.EmergencyNumberTracker; +import com.android.internal.telephony.emergency.EmergencyStateTracker; import com.android.internal.telephony.gsm.GsmMmiCode; import com.android.internal.telephony.gsm.SsData; import com.android.internal.telephony.gsm.SuppServiceNotification; @@ -129,6 +143,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -155,6 +172,8 @@ public class GsmCdmaPhone extends Phone { // Key used to read/write the SIM IMSI used for storing the voice mail private static final String VM_SIM_IMSI = "vm_sim_imsi_key"; /** List of Registrants to receive Supplementary Service Notifications. */ + // Key used to read/write the current sub Id. Updated on SIM loaded. + public static final String CURR_SUBID = "curr_subid"; private RegistrantList mSsnRegistrants = new RegistrantList(); //CDMA @@ -225,10 +244,17 @@ public class GsmCdmaPhone extends Phone { private final RegistrantList mVolteSilentRedialRegistrants = new RegistrantList(); private DialArgs mDialArgs = null; - + private final RegistrantList mEmergencyDomainSelectedRegistrants = new RegistrantList(); private String mImei; private String mImeiSv; private String mVmNumber; + private int mImeiType = IMEI_TYPE_UNKNOWN; + + @VisibleForTesting + public CellBroadcastConfigTracker mCellBroadcastConfigTracker = + CellBroadcastConfigTracker.make(this, null, true); + + private boolean mIsNullCipherAndIntegritySupported = false; // Create Cfu (Call forward unconditional) so that dialing number & // mOnComplete (Message object passed by client) can be packed & @@ -273,6 +299,7 @@ public class GsmCdmaPhone extends Phone { private final CarrierPrivilegesTracker mCarrierPrivilegesTracker; private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener; + private final CallWaitingController mCallWaitingController; // Constructors @@ -342,24 +369,22 @@ public class GsmCdmaPhone extends Phone { mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null); mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null); + mSST.getServiceStateStats().registerDataNetworkControllerCallback(); - if (isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.registerCallback(new SubscriptionManagerServiceCallback( - this::post) { - @Override - public void onUiccApplicationsEnabledChanged(int subId) { - reapplyUiccAppsEnablementIfNeeded(ENABLE_UICC_APPS_MAX_RETRIES); - } - }); - } else { - SubscriptionController.getInstance().registerForUiccAppsEnabled(this, - EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED, null, false); - } + mSubscriptionManagerService.registerCallback(new SubscriptionManagerServiceCallback( + this::post) { + @Override + public void onUiccApplicationsEnabledChanged(int subId) { + reapplyUiccAppsEnablementIfNeeded(ENABLE_UICC_APPS_MAX_RETRIES); + } + }); mLinkBandwidthEstimator = mTelephonyComponentFactory .inject(LinkBandwidthEstimator.class.getName()) .makeLinkBandwidthEstimator(this); + mCallWaitingController = new CallWaitingController(this); + loadTtyMode(); CallManager.getInstance().registerPhone(this); @@ -397,6 +422,17 @@ public class GsmCdmaPhone extends Phone { int newPreferredTtyMode = intent.getIntExtra( TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF); updateUiTtyMode(newPreferredTtyMode); + } else if (TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED.equals(action)) { + if (mPhoneId == intent.getIntExtra( + SubscriptionManager.EXTRA_SLOT_INDEX, + SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { + int simState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE, + TelephonyManager.SIM_STATE_UNKNOWN); + if (simState == TelephonyManager.SIM_STATE_LOADED + && currentSlotSubIdChanged()) { + setNetworkSelectionModeAutomatic(null); + } + } } } }; @@ -453,15 +489,20 @@ public class GsmCdmaPhone extends Phone { mCi.registerForLceInfo(this, EVENT_LINK_CAPACITY_CHANGED, null); mCi.registerForCarrierInfoForImsiEncryption(this, EVENT_RESET_CARRIER_KEY_IMSI_ENCRYPTION, null); + mCi.registerForTriggerImsDeregistration(this, EVENT_IMS_DEREGISTRATION_TRIGGERED, null); + mCi.registerForNotifyAnbr(this, EVENT_TRIGGER_NOTIFY_ANBR, null); IntentFilter filter = new IntentFilter( CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED); filter.addAction(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); + filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED); mContext.registerReceiver(mBroadcastReceiver, filter, android.Manifest.permission.MODIFY_PHONE_STATE, null, Context.RECEIVER_EXPORTED); mCDM = new CarrierKeyDownloadManager(this); mCIM = new CarrierInfoManager(); + + initializeCarrierApps(); } private void initRatSpecific(int precisePhoneType) { @@ -484,8 +525,12 @@ public class GsmCdmaPhone extends Phone { // This is needed to handle phone process crashes mIsPhoneInEcmState = getInEcmMode(); if (mIsPhoneInEcmState) { - // Send a message which will invoke handleExitEmergencyCallbackMode - mCi.exitEmergencyCallbackMode(null); + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(); + } else { + // Send a message which will invoke handleExitEmergencyCallbackMode + mCi.exitEmergencyCallbackMode(null); + } } mCi.setPhoneType(PhoneConstants.PHONE_TYPE_CDMA); @@ -508,11 +553,7 @@ public class GsmCdmaPhone extends Phone { logd("update icc_operator_numeric=" + operatorNumeric); tm.setSimOperatorNumericForPhone(mPhoneId, operatorNumeric); - if (isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.setMccMnc(getSubId(), operatorNumeric); - } else { - SubscriptionController.getInstance().setMccMnc(operatorNumeric, getSubId()); - } + mSubscriptionManagerService.setMccMnc(getSubId(), operatorNumeric); // Sets iso country property by retrieving from build-time system property String iso = ""; @@ -524,11 +565,7 @@ public class GsmCdmaPhone extends Phone { logd("init: set 'gsm.sim.operator.iso-country' to iso=" + iso); tm.setSimCountryIsoForPhone(mPhoneId, iso); - if (isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.setCountryIso(getSubId(), iso); - } else { - SubscriptionController.getInstance().setCountryIso(iso, getSubId()); - } + mSubscriptionManagerService.setCountryIso(getSubId(), iso); // Updates MCC MNC device configuration information logd("update mccmnc=" + operatorNumeric); @@ -540,6 +577,31 @@ public class GsmCdmaPhone extends Phone { } } + /** + * Initialize the carrier apps. + */ + private void initializeCarrierApps() { + // Only perform on the default phone. There is no need to do it twice on the DSDS device. + if (mPhoneId != 0) return; + + logd("initializeCarrierApps"); + mContext.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Remove this line after testing + if (Intent.ACTION_USER_FOREGROUND.equals(intent.getAction())) { + UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); + // If couldn't get current user ID, guess it's 0. + CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), + TelephonyManager.getDefault(), + userHandle != null ? userHandle.getIdentifier() : 0, mContext); + } + } + }, new IntentFilter(Intent.ACTION_USER_FOREGROUND), null, null); + CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), + TelephonyManager.getDefault(), ActivityManager.getCurrentUser(), mContext); + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isPhoneTypeGsm() { return mPrecisePhoneType == PhoneConstants.PHONE_TYPE_GSM; @@ -737,7 +799,10 @@ public class GsmCdmaPhone extends Phone { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void notifyPreciseCallStateChanged() { /* we'd love it if this was package-scoped*/ - super.notifyPreciseCallStateChangedP(); + AsyncResult ar = new AsyncResult(null, this, null); + mPreciseCallStateRegistrants.notifyRegistrants(ar); + + mNotifier.notifyPreciseCallState(this, null, null, null); } public void notifyNewRingingConnection(Connection c) { @@ -1332,8 +1397,7 @@ public class GsmCdmaPhone extends Phone { // emergency number list on another SIM, but is not on theirs. In this case we will use the // emergency number list for this carrier's SIM only. if (useOnlyDialedSimEccList) { - isEmergency = getEmergencyNumberTracker().isEmergencyNumber(dialString, - true /* exactMatch */); + isEmergency = getEmergencyNumberTracker().isEmergencyNumber(dialString); logi("dial; isEmergency=" + isEmergency + " (based on this phone only); globalIsEmergency=" + tm.isEmergencyNumber(dialString)); @@ -1342,6 +1406,12 @@ public class GsmCdmaPhone extends Phone { logi("dial; isEmergency=" + isEmergency + " (based on all phones)"); } + // Undetectable emergeny number indicated by new domain selection service + if (dialArgs.isEmergency) { + logi("dial; isEmergency=" + isEmergency + " (domain selection module)"); + isEmergency = true; + } + /** Check if the call is Wireless Priority Service call */ boolean isWpsCall = dialString != null ? (dialString.startsWith(PREFIX_WPS) || dialString.startsWith(PREFIX_WPS_CLIR_ACTIVATE) @@ -1367,6 +1437,55 @@ public class GsmCdmaPhone extends Phone { boolean useImsForCall = useImsForCall(dialArgs) && (isWpsCall ? allowWpsOverIms : true); + Bundle extras = dialArgs.intentExtras; + if (extras != null && extras.containsKey(PhoneConstants.EXTRA_COMPARE_DOMAIN)) { + int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN); + if (!isEmergency && (!isMmiCode || isPotentialUssdCode)) { + if ((domain == DOMAIN_PS && !useImsForCall) + || (domain == DOMAIN_CS && useImsForCall) + || domain == DOMAIN_UNKNOWN || domain == DOMAIN_CS_PS) { + loge("[Anomaly] legacy-useImsForCall:" + useImsForCall + + ", NCDS-domain:" + domain); + + AnomalyReporter.reportAnomaly( + UUID.fromString("bfae6c2e-ca2f-4121-b167-9cad26a3b353"), + "Domain selection results don't match. useImsForCall:" + + useImsForCall + ", NCDS-domain:" + domain); + } + } + extras.remove(PhoneConstants.EXTRA_COMPARE_DOMAIN); + } + + // Only when the domain selection service is supported, EXTRA_DIAL_DOMAIN extra shall exist. + if (extras != null && extras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN)) { + int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN); + logi("dial domain=" + domain); + useImsForCall = false; + useImsForUt = false; + useImsForEmergency = false; + if (domain == DOMAIN_PS) { + if (isEmergency) { + useImsForEmergency = true; + } else if (!isMmiCode || isPotentialUssdCode) { + useImsForCall = true; + } else { + // should not reach here + loge("dial unexpected Ut domain selection, ignored"); + } + } else if (domain == PhoneConstants.DOMAIN_NON_3GPP_PS) { + if (isEmergency) { + useImsForEmergency = true; + extras.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE, + String.valueOf(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN)); + } else { + // should not reach here + loge("dial DOMAIN_NON_3GPP_PS should be used only for emergency calls"); + } + } + + extras.remove(PhoneConstants.EXTRA_DIAL_DOMAIN); + } + if (DBG) { logi("useImsForCall=" + useImsForCall + ", useOnlyDialedSimEccList=" + useOnlyDialedSimEccList @@ -1463,10 +1582,13 @@ public class GsmCdmaPhone extends Phone { } if (DBG) logd("Trying (non-IMS) CS call"); if (isDialedNumberSwapped && isEmergency) { - // Triggers ECM when CS call ends only for test emergency calls using - // ril.test.emergencynumber. - mIsTestingEmergencyCallbackMode = true; - mCi.testingEmergencyCall(); + // If domain selection is enabled, ECM testing is handled in EmergencyStateTracker + if (!DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + // Triggers ECM when CS call ends only for test emergency calls using + // ril.test.emergencynumber. + mIsTestingEmergencyCallbackMode = true; + mCi.testingEmergencyCall(); + } } chosenPhoneConsumer.accept(this); @@ -1698,7 +1820,7 @@ public class GsmCdmaPhone extends Phone { public void setRadioPower(boolean power, boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, boolean forceApply) { setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply, - Phone.RADIO_POWER_REASON_USER); + TelephonyManager.RADIO_POWER_REASON_USER); } @Override @@ -1708,6 +1830,11 @@ public class GsmCdmaPhone extends Phone { forceApply, reason); } + @Override + public Set getRadioPowerOffReasons() { + return mSST.getRadioPowerOffReasons(); + } + private void storeVoiceMailNumber(String number) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); @@ -1857,6 +1984,11 @@ public class GsmCdmaPhone extends Phone { return mImei; } + @Override + public int getImeiType() { + return mImeiType; + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Override public String getEsn() { @@ -2452,6 +2584,7 @@ public class GsmCdmaPhone extends Phone { } Phone imsPhone = mImsPhone; + if (useSsOverIms(onComplete)) { imsPhone.getOutgoingCallerIdDisplay(onComplete); return; @@ -2534,6 +2667,8 @@ public class GsmCdmaPhone extends Phone { return; } + if (mCallWaitingController.getCallWaiting(onComplete)) return; + Phone imsPhone = mImsPhone; if (useSsOverIms(onComplete)) { imsPhone.getCallWaiting(onComplete); @@ -2585,6 +2720,8 @@ public class GsmCdmaPhone extends Phone { return; } + if (mCallWaitingController.setCallWaiting(enable, serviceClass, onComplete)) return; + Phone imsPhone = mImsPhone; if (useSsOverIms(onComplete)) { imsPhone.setCallWaiting(enable, onComplete); @@ -2614,6 +2751,23 @@ public class GsmCdmaPhone extends Phone { } } + @Override + public int getTerminalBasedCallWaitingState(boolean forCsOnly) { + return mCallWaitingController.getTerminalBasedCallWaitingState(forCsOnly); + } + + @Override + public void setTerminalBasedCallWaitingStatus(int state) { + if (mImsPhone != null) { + mImsPhone.setTerminalBasedCallWaitingStatus(state); + } + } + + @Override + public void setTerminalBasedCallWaitingSupported(boolean supported) { + mCallWaitingController.setTerminalBasedCallWaitingSupported(supported); + } + @Override public void getAvailableNetworks(Message response) { if (isPhoneTypeGsm() || isPhoneTypeCdmaLte()) { @@ -2884,11 +3038,12 @@ public class GsmCdmaPhone extends Phone { private void handleRadioAvailable() { mCi.getBasebandVersion(obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE)); - + mCi.getImei(obtainMessage(EVENT_GET_DEVICE_IMEI_DONE)); mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE)); mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY)); mCi.areUiccApplicationsEnabled(obtainMessage(EVENT_GET_UICC_APPS_ENABLEMENT_DONE)); + handleNullCipherEnabledChange(); startLceAfterRadioIsAvailable(); } @@ -2934,7 +3089,21 @@ public class GsmCdmaPhone extends Phone { handleRadioAvailable(); } break; - + case EVENT_GET_DEVICE_IMEI_DONE : + ar = (AsyncResult)msg.obj; + if (ar.exception != null || ar.result == null) { + loge("Exception received : " + ar.exception); + break; + } + ImeiInfo imeiInfo = (ImeiInfo) ar.result; + if (!TextUtils.isEmpty(imeiInfo.imei)) { + mImeiType = imeiInfo.type; + mImei = imeiInfo.imei; + mImeiSv = imeiInfo.svn; + } else { + // TODO Report telephony anomaly + } + break; case EVENT_GET_DEVICE_IDENTITY_DONE:{ ar = (AsyncResult)msg.obj; @@ -2942,8 +3111,10 @@ public class GsmCdmaPhone extends Phone { break; } String[] respId = (String[])ar.result; - mImei = respId[0]; - mImeiSv = respId[1]; + if (TextUtils.isEmpty(mImei)) { + mImei = respId[0]; + mImeiSv = respId[1]; + } mEsn = respId[2]; mMeid = respId[3]; // some modems return all 0's instead of null/empty string when MEID is unavailable @@ -2968,12 +3139,16 @@ public class GsmCdmaPhone extends Phone { logd("Event EVENT_MODEM_RESET Received" + " isInEcm = " + isInEcm() + " isPhoneTypeGsm = " + isPhoneTypeGsm() + " mImsPhone = " + mImsPhone); if (isInEcm()) { - if (isPhoneTypeGsm()) { - if (mImsPhone != null) { - mImsPhone.handleExitEmergencyCallbackMode(); - } + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(); } else { - handleExitEmergencyCallbackMode(msg); + if (isPhoneTypeGsm()) { + if (mImsPhone != null) { + mImsPhone.handleExitEmergencyCallbackMode(); + } + } else { + handleExitEmergencyCallbackMode(msg); + } } } } @@ -3343,12 +3518,64 @@ public class GsmCdmaPhone extends Phone { logd("EVENT_SUBSCRIPTIONS_CHANGED"); updateUsageSetting(); break; + case EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE: + logd("EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE"); + ar = (AsyncResult) msg.obj; + 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: + logd("EVENT_IMS_DEREGISTRATION_TRIGGERED"); + ar = (AsyncResult) msg.obj; + if (ar.exception == null) { + mImsPhone.triggerImsDeregistration(((int[]) ar.result)[0]); + } else { + Rlog.e(LOG_TAG, "Unexpected unsol with exception", ar.exception); + } + break; + + case EVENT_TRIGGER_NOTIFY_ANBR: + logd("EVENT_TRIGGER_NOTIFY_ANBR"); + ar = (AsyncResult) msg.obj; + if (ar.exception == null) { + if (mImsPhone != null) { + mImsPhone.triggerNotifyAnbr(((int[]) ar.result)[0], ((int[]) ar.result)[1], + ((int[]) ar.result)[2]); + } + } + break; default: super.handleMessage(msg); } } + /** + * Check if a different SIM is inserted at this slot from the last time. Storing last subId + * in SharedPreference for now to detect SIM change. + * + * @return {@code true} if current slot mapping changed; {@code false} otherwise. + */ + private boolean currentSlotSubIdChanged() { + SharedPreferences sp = + PreferenceManager.getDefaultSharedPreferences(mContext); + int storedSubId = sp.getInt(CURR_SUBID + mPhoneId, -1); + boolean changed = storedSubId != getSubId(); + if (changed) { + // Update stored subId + SharedPreferences.Editor editor = sp.edit(); + editor.putInt(CURR_SUBID + mPhoneId, getSubId()); + editor.apply(); + } + Rlog.d(LOG_TAG, "currentSlotSubIdChanged: changed=" + changed); + return changed; + } + public UiccCardApplication getUiccCardApplication() { if (isPhoneTypeGsm()) { return mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP); @@ -3715,6 +3942,10 @@ public class GsmCdmaPhone extends Phone { //CDMA private void handleEnterEmergencyCallbackMode(Message msg) { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + Rlog.d(LOG_TAG, "DomainSelection enabled: ignore ECBM enter event."); + return; + } if (DBG) { Rlog.d(LOG_TAG, "handleEnterEmergencyCallbackMode, isInEcm()=" + isInEcm()); @@ -3738,6 +3969,10 @@ public class GsmCdmaPhone extends Phone { //CDMA private void handleExitEmergencyCallbackMode(Message msg) { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + Rlog.d(LOG_TAG, "DomainSelection enabled: ignore ECBM exit event."); + return; + } AsyncResult ar = (AsyncResult)msg.obj; if (DBG) { Rlog.d(LOG_TAG, "handleExitEmergencyCallbackMode,ar.exception , isInEcm=" @@ -3780,6 +4015,7 @@ public class GsmCdmaPhone extends Phone { * otherwise, restart Ecm timer and notify apps the timer is restarted. */ public void handleTimerInEmergencyCallbackMode(int action) { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) return; switch(action) { case CANCEL_ECM_TIMER: removeCallbacks(mExitEcmRunnable); @@ -4193,6 +4429,7 @@ public class GsmCdmaPhone extends Phone { @Override public void setImsRegistrationState(boolean registered) { mSST.setImsRegistrationState(registered); + mCallWaitingController.setImsRegistrationState(registered); } @Override @@ -4256,6 +4493,12 @@ public class GsmCdmaPhone extends Phone { + ")"); pw.println(" mUiccApplicationsEnabled=" + mUiccApplicationsEnabled); pw.flush(); + try { + mCallWaitingController.dump(pw); + } catch (Exception e) { + e.printStackTrace(); + } + pw.flush(); } @Override @@ -4374,7 +4617,7 @@ public class GsmCdmaPhone extends Phone { if (subInfo == null || TextUtils.isEmpty(subInfo.getCountryIso())) { return null; } - return subInfo.getCountryIso().toUpperCase(); + return subInfo.getCountryIso().toUpperCase(Locale.ROOT); } public void notifyEcbmTimerReset(Boolean flag) { @@ -4480,6 +4723,27 @@ public class GsmCdmaPhone extends Phone { mVolteSilentRedialRegistrants.notifyRegistrants(ar); } + /** {@inheritDoc} */ + @Override + public void registerForEmergencyDomainSelected( + @NonNull Handler h, int what, @Nullable Object obj) { + mEmergencyDomainSelectedRegistrants.addUnique(h, what, obj); + } + + /** {@inheritDoc} */ + @Override + public void unregisterForEmergencyDomainSelected(@NonNull Handler h) { + mEmergencyDomainSelectedRegistrants.remove(h); + } + + /** {@inheritDoc} */ + @Override + public void notifyEmergencyDomainSelected(@TransportType int transportType) { + logd("notifyEmergencyDomainSelected transportType=" + transportType); + mEmergencyDomainSelectedRegistrants.notifyRegistrants( + new AsyncResult(null, transportType, null)); + } + /** * Sets the SIM voice message waiting indicator records. * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported @@ -4654,18 +4918,12 @@ public class GsmCdmaPhone extends Phone { return; } - SubscriptionInfo info; - if (isSubscriptionManagerServiceEnabled()) { - info = mSubscriptionManagerService - .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag()) - .stream() - .filter(subInfo -> subInfo.getIccId().equals(IccUtils.stripTrailingFs(iccId))) - .findFirst() - .orElse(null); - } else { - info = SubscriptionController.getInstance().getSubInfoForIccId( - IccUtils.stripTrailingFs(iccId)); - } + SubscriptionInfo info = mSubscriptionManagerService + .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag()) + .stream() + .filter(subInfo -> subInfo.getIccId().equals(IccUtils.stripTrailingFs(iccId))) + .findFirst() + .orElse(null); logd("reapplyUiccAppsEnablementIfNeeded: retries=" + retries + ", subInfo=" + info); @@ -4770,18 +5028,10 @@ public class GsmCdmaPhone extends Phone { config.getBoolean(CarrierConfigManager.KEY_VONR_ON_BY_DEFAULT_BOOL); int setting = -1; - if (isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(getSubId()); - if (subInfo != null) { - setting = subInfo.getNrAdvancedCallingEnabled(); - } - } else { - String result = SubscriptionController.getInstance().getSubscriptionProperty( - getSubId(), SubscriptionManager.NR_ADVANCED_CALLING_ENABLED); - if (result != null) { - setting = Integer.parseInt(result); - } + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(getSubId()); + if (subInfo != null) { + setting = subInfo.getNrAdvancedCallingEnabled(); } logd("VoNR setting from telephony.db:" @@ -4856,6 +5106,22 @@ public class GsmCdmaPhone extends Phone { return mIccSmsInterfaceManager.getInboundSmsHandler(is3gpp2); } + /** + * Return current cell broadcast ranges. + */ + public List getCellBroadcastIdRanges() { + return mCellBroadcastConfigTracker.getCellBroadcastIdRanges(); + } + + /** + * Set reception of cell broadcast messages with the list of the given ranges. + */ + @Override + public void setCellBroadcastIdRanges( + @NonNull List ranges, Consumer callback) { + mCellBroadcastConfigTracker.setCellBroadcastIdRanges(ranges, callback); + } + /** * The following function checks if supplementary service request is blocked due to FDN. * @param requestType request type associated with the supplementary service @@ -4867,4 +5133,21 @@ public class GsmCdmaPhone extends Phone { ArrayList controlStrings = GsmMmiCode.getControlStrings(requestType, serviceType); return FdnUtils.isSuppServiceRequestBlockedByFdn(mPhoneId, controlStrings, getCountryIso()); } -} \ No newline at end of file + + @Override + public void handleNullCipherEnabledChange() { + if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CELLULAR_SECURITY, + TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, true)) { + logi("Not handling null cipher update. Feature disabled by DeviceConfig."); + return; + } + mCi.setNullCipherAndIntegrityEnabled( + getNullCipherAndIntegrityEnabledPreference(), + obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE)); + } + + @Override + public boolean isNullCipherAndIntegritySupported() { + return mIsNullCipherAndIntegritySupported; + } +} diff --git a/src/java/com/android/internal/telephony/HalVersion.java b/src/java/com/android/internal/telephony/HalVersion.java index f83d790f2017c06e61811f94ea52a8dbe75ba89e..c05111b256f79c7853f5206291e5fa1e03bf0189 100644 --- a/src/java/com/android/internal/telephony/HalVersion.java +++ b/src/java/com/android/internal/telephony/HalVersion.java @@ -25,6 +25,9 @@ import java.util.Objects; */ public class HalVersion implements Comparable { + /** The HAL Version indicating that the version is unsupported */ + public static final HalVersion UNSUPPORTED = new HalVersion(-2, -2); + /** The HAL Version indicating that the version is unknown or invalid */ public static final HalVersion UNKNOWN = new HalVersion(-1, -1); diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java index 742cc900b98d35c1be0d39cab40a4e8408f0f8e5..ab62aa4ac147ed14bc65eea722af226d35050d63 100644 --- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java +++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java @@ -39,6 +39,7 @@ import com.android.internal.telephony.uicc.UiccProfile; import com.android.telephony.Rlog; import java.util.List; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -337,7 +338,10 @@ public class IccPhoneBookInterfaceManager { } efid = updateEfForIccType(efid); - if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase()); + if (DBG) { + logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid) + .toUpperCase(Locale.ROOT)); + } checkThread(); Request loadRequest = new Request(); diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java index 7a128c034dbda3afd074239e7fe7ed1686ab85d1..b7c7e7b538c03bf7a8d832380bcfe8374538b960 100644 --- a/src/java/com/android/internal/telephony/IccProvider.java +++ b/src/java/com/android/internal/telephony/IccProvider.java @@ -37,6 +37,7 @@ import com.android.internal.telephony.uicc.IccConstants; import com.android.telephony.Rlog; import java.util.List; +import java.util.Locale; /** * {@hide} @@ -424,7 +425,7 @@ public class IccProvider extends ContentProvider { private MatrixCursor loadFromEf(int efType, int subId) { if (DBG) log("loadFromEf: efType=0x" + - Integer.toHexString(efType).toUpperCase() + ", subscription=" + subId); + Integer.toHexString(efType).toUpperCase(Locale.ROOT) + ", subscription=" + subId); List adnRecords = null; try { diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java index d3e6a0d728ebd4a6d38c3aaf5e4902516ab7372b..2d7763179cda39b0805df18bf7fcc384b0d05486 100644 --- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java +++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java @@ -176,6 +176,41 @@ public class IccSmsInterfaceManager { mSmsPermissions = smsPermissions; } + /** + * PhoneFactory Dependencies for testing. + */ + @VisibleForTesting + public interface PhoneFactoryProxy { + Phone getPhone(int index); + Phone getDefaultPhone(); + Phone[] getPhones(); + } + + private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() { + @Override + public Phone getPhone(int index) { + return PhoneFactory.getPhone(index); + } + + @Override + public Phone getDefaultPhone() { + return PhoneFactory.getDefaultPhone(); + } + + @Override + public Phone[] getPhones() { + return PhoneFactory.getPhones(); + } + }; + + /** + * Overrides PhoneFactory dependencies for testing. + */ + @VisibleForTesting + public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) { + mPhoneFactoryProxy = proxy; + } + private void enforceNotOnHandlerThread(String methodName) { if (Looper.myLooper() == mHandler.getLooper()) { throw new RuntimeException("This method " + methodName + " will deadlock if called from" @@ -457,11 +492,11 @@ public class IccSmsInterfaceManager { */ public void sendText(String callingPackage, String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, - boolean persistMessageForNonDefaultSmsApp, long messageId) { + boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck) { sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent, persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */, - messageId); + messageId, skipShortCodeCheck); } /** @@ -481,6 +516,16 @@ public class IccSmsInterfaceManager { SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm, 0L /* messageId */); } + + private void sendTextInternal(String callingPackage, String destAddr, String scAddr, + String text, PendingIntent sentIntent, PendingIntent deliveryIntent, + boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore, + int validityPeriod, boolean isForVvm, long messageId) { + sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent, + persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod, isForVvm, + messageId, false); + } + /** * Send a text based SMS. * @@ -527,12 +572,13 @@ public class IccSmsInterfaceManager { * Any Other values including negative considered as Invalid Validity Period of the message. * @param messageId An id that uniquely identifies the message requested to be sent. * Used for logging and diagnostics purposes. The id may be 0. + * @param skipShortCodeCheck Skip check for short code type destination address. */ private void sendTextInternal(String callingPackage, String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore, - int validityPeriod, boolean isForVvm, long messageId) { + int validityPeriod, boolean isForVvm, long messageId, boolean skipShortCodeCheck) { if (Rlog.isLoggable("SMS", Log.VERBOSE)) { log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr + " text='" + text + "' sentIntent=" + sentIntent + " deliveryIntent=" @@ -544,7 +590,7 @@ public class IccSmsInterfaceManager { destAddr = filterDestAddress(destAddr); mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp, - priority, expectMore, validityPeriod, isForVvm, messageId); + priority, expectMore, validityPeriod, isForVvm, messageId, skipShortCodeCheck); } /** @@ -862,6 +908,7 @@ public class IccSmsInterfaceManager { public String getSmscAddressFromIccEf(String callingPackage) { if (!mSmsPermissions.checkCallingOrSelfCanGetSmscAddress( callingPackage, "getSmscAddressFromIccEf")) { + loge("Caller do not have permission to call GetSmscAddress"); return null; } enforceNotOnHandlerThread("getSmscAddressFromIccEf"); @@ -883,6 +930,7 @@ public class IccSmsInterfaceManager { public boolean setSmscAddressOnIccEf(String callingPackage, String smsc) { if (!mSmsPermissions.checkCallingOrSelfCanSetSmscAddress( callingPackage, "setSmscAddressOnIccEf")) { + loge("Caller do not have permission to call SetSmscAddress"); return false; } enforceNotOnHandlerThread("setSmscAddressOnIccEf"); @@ -1452,11 +1500,27 @@ public class IccSmsInterfaceManager { return null; } - private void notifyIfOutgoingEmergencySms(String destAddr) { + @VisibleForTesting + public void notifyIfOutgoingEmergencySms(String destAddr) { + Phone[] allPhones = mPhoneFactoryProxy.getPhones(); EmergencyNumber emergencyNumber = mPhone.getEmergencyNumberTracker().getEmergencyNumber( destAddr); if (emergencyNumber != null) { mPhone.notifyOutgoingEmergencySms(emergencyNumber); + } else if (allPhones.length > 1) { + // If there are multiple active SIMs, check all instances: + for (Phone phone : allPhones) { + // If the current iteration was already checked, skip: + if (phone.getPhoneId() == mPhone.getPhoneId()) { + continue; + } + emergencyNumber = phone.getEmergencyNumberTracker() + .getEmergencyNumber(destAddr); + if (emergencyNumber != null) { + mPhone.notifyOutgoingEmergencySms(emergencyNumber); + break; + } + } } } diff --git a/src/java/com/android/internal/telephony/ImsIndication.java b/src/java/com/android/internal/telephony/ImsIndication.java new file mode 100644 index 0000000000000000000000000000000000000000..00652f888db4f3f7627a7139ed713f01bbacf855 --- /dev/null +++ b/src/java/com/android/internal/telephony/ImsIndication.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import static android.telephony.TelephonyManager.HAL_SERVICE_IMS; + +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CONNECTION_SETUP_FAILURE; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NOTIFY_ANBR; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION; + +import android.hardware.radio.ims.IRadioImsIndication; +import android.os.AsyncResult; +import android.telephony.ims.feature.ConnectionFailureInfo; + +/** + * Interface declaring unsolicited radio indications for IMS APIs. + */ +public class ImsIndication extends IRadioImsIndication.Stub { + private final RIL mRil; + + public ImsIndication(RIL ril) { + mRil = ril; + } + + @Override + public String getInterfaceHash() { + return IRadioImsIndication.HASH; + } + + @Override + public int getInterfaceVersion() { + return IRadioImsIndication.VERSION; + } + + /** + * Fired by radio when any IMS traffic is not sent to network due to any failure + * on cellular networks. + * + * @param indicationType Type of radio indication. + * @param token The token provided by {@link #startImsTraffic}. + * @param failureInfo Connection failure information. + */ + public void onConnectionSetupFailure(int indicationType, int token, + android.hardware.radio.ims.ConnectionFailureInfo failureInfo) { + mRil.processIndication(HAL_SERVICE_IMS, indicationType); + + Object[] response = new Object[2]; + response[0] = token; + response[1] = new ConnectionFailureInfo( + RILUtils.convertHalConnectionFailureReason(failureInfo.failureReason), + failureInfo.causeCode, failureInfo.waitTimeMillis); + + if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CONNECTION_SETUP_FAILURE, response); + + mRil.mConnectionSetupFailureRegistrants.notifyRegistrants( + new AsyncResult(null, response, null)); + } + + /** + * Fired by radio when ANBR is received form the network. + * + * @param indicationType Type of radio indication. + * @param qosSessionId QoS session ID is used to identify media stream such as audio or video. + * @param imsdirection Direction of this packet stream (e.g. uplink or downlink). + * @param bitsPerSecond The recommended bit rate for the UE + * for a specific logical channel and a specific direction by the network. + */ + public void notifyAnbr(int indicationType, int qosSessionId, int imsdirection, + int bitsPerSecond) { + mRil.processIndication(HAL_SERVICE_IMS, indicationType); + + int[] response = new int[3]; + response[0] = qosSessionId; + response[1] = imsdirection; + response[2] = bitsPerSecond; + + if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NOTIFY_ANBR, response); + + mRil.mNotifyAnbrRegistrants.notifyRegistrants(new AsyncResult(null, response, null)); + } + + /** + * Requests IMS stack to perform graceful IMS deregistration before radio performing + * network detach in the events of SIM remove, refresh or and so on. The radio waits for + * the IMS deregistration, which will be notified by telephony via + * {@link IRadioIms#updateImsRegistrationInfo()}, or a certain timeout interval to start + * the network detach procedure. + * + * @param indicationType Type of radio indication. + * @param reason the reason why the deregistration is triggered. + */ + public void triggerImsDeregistration(int indicationType, int reason) { + mRil.processIndication(HAL_SERVICE_IMS, indicationType); + + if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION, reason); + + int[] response = new int[1]; + response[0] = RILUtils.convertHalDeregistrationReason(reason); + + mRil.mTriggerImsDeregistrationRegistrants.notifyRegistrants( + new AsyncResult(null, response, null)); + } +} diff --git a/src/java/com/android/internal/telephony/ImsResponse.java b/src/java/com/android/internal/telephony/ImsResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..1adc000063a825dc7cb300243dd0fffacfe36cdf --- /dev/null +++ b/src/java/com/android/internal/telephony/ImsResponse.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import static android.telephony.TelephonyManager.HAL_SERVICE_IMS; +import static android.telephony.ims.feature.MmTelFeature.ImsTrafficSessionCallbackWrapper.INVALID_TOKEN; + +import android.hardware.radio.RadioError; +import android.hardware.radio.RadioResponseInfo; +import android.hardware.radio.ims.IRadioImsResponse; +import android.telephony.ims.feature.ConnectionFailureInfo; + +/** + * Interface declaring response functions to solicited radio requests for IMS APIs. + */ +public class ImsResponse extends IRadioImsResponse.Stub { + private final RIL mRil; + + public ImsResponse(RIL ril) { + mRil = ril; + } + + @Override + public String getInterfaceHash() { + return IRadioImsResponse.HASH; + } + + @Override + public int getInterfaceVersion() { + return IRadioImsResponse.VERSION; + } + + /** + * @param info Response info struct containing response type, serial no. and error. + */ + public void setSrvccCallInfoResponse(RadioResponseInfo info) { + RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info); + } + + /** + * @param info Response info struct containing response type, serial no. and error. + */ + public void updateImsRegistrationInfoResponse(RadioResponseInfo info) { + RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error. + * @param failureInfo Failure information. + */ + public void startImsTrafficResponse(RadioResponseInfo responseInfo, + android.hardware.radio.ims.ConnectionFailureInfo failureInfo) { + RILRequest rr = mRil.processResponse(HAL_SERVICE_IMS, responseInfo); + + if (rr != null) { + Object[] response = { new Integer(INVALID_TOKEN), null }; + if (responseInfo.error == RadioError.NONE) { + if (failureInfo != null) { + response[1] = new ConnectionFailureInfo( + RILUtils.convertHalConnectionFailureReason(failureInfo.failureReason), + failureInfo.causeCode, failureInfo.waitTimeMillis); + } + RadioResponse.sendMessageResponse(rr.mResult, response); + } + mRil.processResponseDone(rr, responseInfo, response); + } + } + + /** + * @param info Response info struct containing response type, serial no. and error. + */ + public void stopImsTrafficResponse(RadioResponseInfo info) { + RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info); + } + + /** + * @param info Response info struct containing response type, serial no. and error. + */ + public void triggerEpsFallbackResponse(RadioResponseInfo info) { + RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info); + } + + /** + * @param info Response info struct containing response type, serial no. and error. + */ + public void sendAnbrQueryResponse(RadioResponseInfo info) { + RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info); + } + + /** + * @param info Response info struct containing response type, serial no. and error. + */ + public void updateImsCallStatusResponse(RadioResponseInfo info) { + RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info); + } +} diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java index b9f896c155fdf86e7ab67aaa1c899969acff2b30..90885fe6e399b38caedd09d1972715aff5a151b7 100644 --- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java +++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java @@ -16,6 +16,7 @@ package com.android.internal.telephony; +import android.app.Activity; import android.content.Context; import android.os.Binder; import android.os.Message; @@ -43,7 +44,9 @@ import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.util.SMSDispatcherUtil; import com.android.telephony.Rlog; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -58,6 +61,7 @@ public class ImsSmsDispatcher extends SMSDispatcher { private static final String TAG = "ImsSmsDispatcher"; private static final int CONNECT_DELAY_MS = 5000; // 5 seconds; + public static final int MAX_SEND_RETRIES_OVER_IMS = MAX_SEND_RETRIES; /** * Creates FeatureConnector instances for ImsManager, used during testing to inject mock @@ -72,6 +76,7 @@ public class ImsSmsDispatcher extends SMSDispatcher { FeatureConnector.Listener listener, Executor executor); } + public List mMemoryAvailableNotifierList = new ArrayList(); @VisibleForTesting public Map mTrackers = new ConcurrentHashMap<>(); @VisibleForTesting @@ -139,6 +144,37 @@ public class ImsSmsDispatcher extends SMSDispatcher { }; private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() { + @Override + public void onMemoryAvailableResult(int token, @SendStatusResult int status, + int networkReasonCode) { + final long identity = Binder.clearCallingIdentity(); + try { + logd("onMemoryAvailableResult token=" + token + " status=" + status + + " networkReasonCode=" + networkReasonCode); + if (!mMemoryAvailableNotifierList.contains(token)) { + loge("onMemoryAvailableResult Invalid token"); + return; + } + mMemoryAvailableNotifierList.remove(Integer.valueOf(token)); + + /** + * The Retrans flag is set and reset As per section 6.3.3.1.2 in TS 124011 + * Note: Assuming that SEND_STATUS_ERROR_RETRY is sent in case of temporary failure + */ + if (status == ImsSmsImplBase.SEND_STATUS_ERROR_RETRY) { + if (!mRPSmmaRetried) { + sendMessageDelayed(obtainMessage(EVENT_RETRY_SMMA), SEND_RETRY_DELAY); + mRPSmmaRetried = true; + } else { + mRPSmmaRetried = false; + } + } else { + mRPSmmaRetried = false; + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } @Override public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, @SmsManager.Result int reason, int networkReasonCode) { @@ -170,10 +206,19 @@ public class ImsSmsDispatcher extends SMSDispatcher { mTrackers.remove(token); break; case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY: - if (tracker.mRetryCount < MAX_SEND_RETRIES) { + int maxRetryCountOverIms = getMaxRetryCountOverIms(); + if (tracker.mRetryCount < getMaxSmsRetryCount()) { + if (maxRetryCountOverIms < getMaxSmsRetryCount() + && tracker.mRetryCount >= maxRetryCountOverIms) { + tracker.mRetryCount += 1; + mTrackers.remove(token); + fallbackToPstn(tracker); + break; + } tracker.mRetryCount += 1; sendMessageDelayed( - obtainMessage(EVENT_SEND_RETRY, tracker), SEND_RETRY_DELAY); + obtainMessage(EVENT_SEND_RETRY, tracker), + getSmsRetryDelayValue()); } else { tracker.onFailed(mContext, reason, networkReasonCode); mTrackers.remove(token); @@ -193,6 +238,7 @@ public class ImsSmsDispatcher extends SMSDispatcher { SmsConstants.FORMAT_3GPP2.equals(getFormat()), status == ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, reason, + networkReasonCode, tracker.mMessageId, tracker.isFromDefaultSmsApplication(mContext), tracker.getInterval()); @@ -248,6 +294,10 @@ public class ImsSmsDispatcher extends SMSDispatcher { mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED; break; + case Activity.RESULT_OK: + // class2 message saving to SIM operation is in progress, defer ack + // until saving to SIM is success/failure + return; default: mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC; break; @@ -263,7 +313,7 @@ public class ImsSmsDispatcher extends SMSDispatcher { } catch (ImsException e) { loge("Failed to acknowledgeSms(). Error: " + e.getMessage()); } - }, true /* ignoreClass */, true /* isOverIms */); + }, true /* ignoreClass */, true /* isOverIms */, token); } finally { Binder.restoreCallingIdentity(identity); } @@ -277,6 +327,10 @@ public class ImsSmsDispatcher extends SMSDispatcher { logd("SMS retry.."); sendSms((SmsTracker) msg.obj); break; + case EVENT_RETRY_SMMA: + logd("SMMA Notification retry.."); + onMemoryAvailable(); + break; default: super.handleMessage(msg); } @@ -295,6 +349,9 @@ public class ImsSmsDispatcher extends SMSDispatcher { mImsManager = manager; setListeners(); mIsImsServiceUp = true; + + /* set ImsManager */ + mSmsDispatchersController.setImsManager(mImsManager); } } @@ -309,6 +366,9 @@ public class ImsSmsDispatcher extends SMSDispatcher { synchronized (mLock) { mImsManager = null; mIsImsServiceUp = false; + + /* unset ImsManager */ + mSmsDispatchersController.setImsManager(null); } } }, this::post); @@ -393,6 +453,81 @@ public class ImsSmsDispatcher extends SMSDispatcher { } } + @Override + public int getMaxSmsRetryCount() { + int retryCount = MAX_SEND_RETRIES; + CarrierConfigManager mConfigManager; + + mConfigManager = (CarrierConfigManager) mContext.getSystemService( + Context.CARRIER_CONFIG_SERVICE); + + if (mConfigManager != null) { + PersistableBundle carrierConfig = mConfigManager.getConfigForSubId( + getSubId()); + + if (carrierConfig != null) { + retryCount = carrierConfig.getInt( + CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_COUNT_INT); + } + } + + Rlog.d(TAG, "Retry Count: " + retryCount); + + return retryCount; + } + + /** + * Returns the number of times SMS can be sent over IMS + * + * @return retry count over Ims from carrier configuration + */ + @VisibleForTesting + public int getMaxRetryCountOverIms() { + int retryCountOverIms = MAX_SEND_RETRIES_OVER_IMS; + CarrierConfigManager mConfigManager; + + mConfigManager = (CarrierConfigManager) mContext.getSystemService(Context + .CARRIER_CONFIG_SERVICE); + + if (mConfigManager != null) { + PersistableBundle carrierConfig = mConfigManager.getConfigForSubId( + getSubId()); + + + if (carrierConfig != null) { + retryCountOverIms = carrierConfig.getInt( + CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT); + } + } + + Rlog.d(TAG, "Retry Count Over Ims: " + retryCountOverIms); + + return retryCountOverIms; + } + + @Override + public int getSmsRetryDelayValue() { + int retryDelay = SEND_RETRY_DELAY; + CarrierConfigManager mConfigManager; + + mConfigManager = (CarrierConfigManager) mContext.getSystemService( + Context.CARRIER_CONFIG_SERVICE); + + if (mConfigManager != null) { + PersistableBundle carrierConfig = mConfigManager.getConfigForSubId( + getSubId()); + + if (carrierConfig != null) { + retryDelay = carrierConfig.getInt( + CarrierConfigManager.ImsSms.KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT); + } + } + + Rlog.d(TAG, "Retry delay: " + retryDelay); + + return retryDelay; + } + @Override protected boolean shouldBlockSmsForEcbm() { // We should not block outgoing SMS during ECM on IMS. It only applies to outgoing CDMA @@ -435,6 +570,24 @@ public class ImsSmsDispatcher extends SMSDispatcher { return SMSDispatcherUtil.calculateLength(isCdmaMo(), messageBody, use7bitOnly); } + /** + * Send the Memory Available Event to the ImsService + */ + public void onMemoryAvailable() { + logd("onMemoryAvailable "); + int token = mNextToken.incrementAndGet(); + try { + logd("onMemoryAvailable: token = " + token); + mMemoryAvailableNotifierList.add(token); + getImsManager().onMemoryAvailable(token); + } catch (ImsException e) { + loge("onMemoryAvailable failed: " + e.getMessage()); + if (mMemoryAvailableNotifierList.contains(token)) { + mMemoryAvailableNotifierList.remove(Integer.valueOf(token)); + } + } + } + @Override public void sendSms(SmsTracker tracker) { logd("sendSms: " diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java index 8f43dce58fb3b14630d94b6470050f5d1dd9ae88..91667199b36a5e3b42f7360f92110b9bc3de5537 100644 --- a/src/java/com/android/internal/telephony/InboundSmsHandler.java +++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java @@ -45,7 +45,6 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.SQLException; @@ -53,11 +52,10 @@ import android.net.Uri; import android.os.AsyncResult; import android.os.Build; import android.os.Bundle; +import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.PowerWhitelistManager; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; @@ -185,6 +183,7 @@ public abstract class InboundSmsHandler extends StateMachine { /** BroadcastReceiver timed out waiting for an intent */ public static final int EVENT_RECEIVER_TIMEOUT = 9; + /** Wakelock release delay when returning to idle state. */ private static final int WAKELOCK_TIMEOUT = 3000; @@ -289,8 +288,8 @@ public abstract class InboundSmsHandler extends StateMachine { * @param storageMonitor the SmsStorageMonitor to check for storage availability */ protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor, - Phone phone) { - super(name); + Phone phone, Looper looper) { + super(name, looper); mContext = context; mStorageMonitor = storageMonitor; @@ -501,7 +500,6 @@ public abstract class InboundSmsHandler extends StateMachine { case EVENT_RETURN_TO_IDLE: // already in idle state; ignore return HANDLED; - case EVENT_BROADCAST_COMPLETE: case EVENT_START_ACCEPTING_SMS: default: @@ -540,7 +538,8 @@ public abstract class InboundSmsHandler extends StateMachine { case EVENT_INJECT_SMS: // handle new injected SMS - handleInjectSms((AsyncResult) msg.obj, msg.arg1 == 1 /* isOverIms */); + handleInjectSms((AsyncResult) msg.obj, msg.arg1 == 1 /* isOverIms */, + msg.arg2 /* token */); sendMessage(EVENT_RETURN_TO_IDLE); return HANDLED; @@ -663,7 +662,6 @@ public abstract class InboundSmsHandler extends StateMachine { } } } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void handleNewSms(AsyncResult ar) { if (ar.exception != null) { @@ -674,7 +672,7 @@ public abstract class InboundSmsHandler extends StateMachine { int result; try { SmsMessage sms = (SmsMessage) ar.result; - result = dispatchMessage(sms.mWrappedSmsMessage, SOURCE_NOT_INJECTED); + result = dispatchMessage(sms.mWrappedSmsMessage, SOURCE_NOT_INJECTED, 0 /*unused*/); } catch (RuntimeException ex) { loge("Exception dispatching message", ex); result = RESULT_SMS_DISPATCH_FAILURE; @@ -693,7 +691,7 @@ public abstract class InboundSmsHandler extends StateMachine { * @param ar is the AsyncResult that has the SMS PDU to be injected. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private void handleInjectSms(AsyncResult ar, boolean isOverIms) { + private void handleInjectSms(AsyncResult ar, boolean isOverIms, int token) { int result; SmsDispatchersController.SmsInjectionCallback callback = null; try { @@ -705,7 +703,7 @@ public abstract class InboundSmsHandler extends StateMachine { } else { @SmsSource int smsSource = isOverIms ? SOURCE_INJECTED_FROM_IMS : SOURCE_INJECTED_FROM_UNKNOWN; - result = dispatchMessage(sms.mWrappedSmsMessage, smsSource); + result = dispatchMessage(sms.mWrappedSmsMessage, smsSource, token); } } catch (RuntimeException ex) { loge("Exception dispatching message", ex); @@ -726,7 +724,7 @@ public abstract class InboundSmsHandler extends StateMachine { * @return a result code from {@link android.provider.Telephony.Sms.Intents}, * or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC */ - private int dispatchMessage(SmsMessageBase smsb, @SmsSource int smsSource) { + private int dispatchMessage(SmsMessageBase smsb, @SmsSource int smsSource, int token) { // If sms is null, there was a parsing error. if (smsb == null) { loge("dispatchSmsMessage: message is null"); @@ -740,20 +738,7 @@ public abstract class InboundSmsHandler extends StateMachine { return Intents.RESULT_SMS_HANDLED; } - // onlyCore indicates if the device is in cryptkeeper - boolean onlyCore = false; - try { - onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService("package")) - .isOnlyCoreApps(); - } catch (RemoteException e) { - } - if (onlyCore) { - // Device is unable to receive SMS in encrypted state - log("Received a short message in encrypted state. Rejecting."); - return Intents.RESULT_SMS_RECEIVED_WHILE_ENCRYPTED; - } - - int result = dispatchMessageRadioSpecific(smsb, smsSource); + int result = dispatchMessageRadioSpecific(smsb, smsSource, token); // In case of error, add to metrics. This is not required in case of success, as the // data will be tracked when the message is processed (processMessagePart). @@ -775,7 +760,7 @@ public abstract class InboundSmsHandler extends StateMachine { * or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC */ protected abstract int dispatchMessageRadioSpecific(SmsMessageBase smsb, - @SmsSource int smsSource); + @SmsSource int smsSource, int token); /** * Send an acknowledge message to the SMSC. @@ -875,7 +860,6 @@ public abstract class InboundSmsHandler extends StateMachine { * RESULT_SMS_DISPATCH_FAILURE
* RESULT_SMS_NULL_PDU
* RESULT_SMS_NULL_MESSAGE
- * RESULT_SMS_RECEIVED_WHILE_ENCRYPTED
* RESULT_SMS_DATABASE_ERROR
* RESULT_SMS_INVALID_URI
*/ @@ -1159,7 +1143,7 @@ public abstract class InboundSmsHandler extends StateMachine { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void showNewMessageNotification() { // Do not show the notification on non-FBE devices. - if (!StorageManager.isFileEncryptedNativeOrEmulated()) { + if (!StorageManager.isFileEncrypted()) { return; } log("Show new message notification."); @@ -1453,12 +1437,14 @@ public abstract class InboundSmsHandler extends StateMachine { intent.putExtra("messageId", messageId); } + UserHandle userHandle = null; if (destPort == -1) { intent.setAction(Intents.SMS_DELIVER_ACTION); // Direct the intent to only the default SMS app. If we can't find a default SMS app // then sent it to all broadcast receivers. - // We are deliberately delivering to the primary user's default SMS App. - ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true); + userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId); + ComponentName componentName = SmsApplication.getDefaultSmsApplicationAsUser(mContext, + true, userHandle); if (componentName != null) { // Deliver SMS message only to this receiver. intent.setComponent(componentName); @@ -1482,9 +1468,12 @@ public abstract class InboundSmsHandler extends StateMachine { intent.setComponent(null); } + if (userHandle == null) { + userHandle = UserHandle.SYSTEM; + } Bundle options = handleSmsWhitelisting(intent.getComponent(), isClass0); dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS, - AppOpsManager.OPSTR_RECEIVE_SMS, options, resultReceiver, UserHandle.SYSTEM, subId); + AppOpsManager.OPSTR_RECEIVE_SMS, options, resultReceiver, userHandle, subId); } /** diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java old mode 100755 new mode 100644 index 31d66864840b311304800c5fdbd1d6f53a773f5a..de854fa7ca1603c4f8d3e8170d4431c9ac14dc14 --- a/src/java/com/android/internal/telephony/LocaleTracker.java +++ b/src/java/com/android/internal/telephony/LocaleTracker.java @@ -30,6 +30,7 @@ import android.os.AsyncResult; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.UserHandle; import android.sysprop.TelephonyProperties; import android.telephony.CellInfo; import android.telephony.ServiceState; @@ -558,7 +559,7 @@ public class LocaleTracker extends Handler { intent.putExtra(TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY, getLastKnownCountryIso()); SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); - mPhone.getContext().sendBroadcast(intent); + mPhone.getContext().sendBroadcastAsUser(intent, UserHandle.ALL); } // Pass the geographical country information to the telephony time zone detection code. diff --git a/src/java/com/android/internal/telephony/MessagingIndication.java b/src/java/com/android/internal/telephony/MessagingIndication.java index 96e74cc7679224a37600856c1a7c7b2d1d20fec2..5814e3db6f315d3b55f83964510dd985e2905579 100644 --- a/src/java/com/android/internal/telephony/MessagingIndication.java +++ b/src/java/com/android/internal/telephony/MessagingIndication.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING; + import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_CDMA_NEW_SMS; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS; @@ -47,7 +49,7 @@ public class MessagingIndication extends IRadioMessagingIndication.Stub { */ public void cdmaNewSms(int indicationType, android.hardware.radio.messaging.CdmaSmsMessage msg) { - mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS); @@ -63,7 +65,7 @@ public class MessagingIndication extends IRadioMessagingIndication.Stub { * @param indicationType Type of radio indication */ public void cdmaRuimSmsStorageFull(int indicationType) { - mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL); @@ -82,7 +84,7 @@ public class MessagingIndication extends IRadioMessagingIndication.Stub { * BTS as coded in 3GPP 23.041 Section 9.4.2.2 */ public void newBroadcastSms(int indicationType, byte[] data) { - mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogvRet( @@ -101,7 +103,7 @@ public class MessagingIndication extends IRadioMessagingIndication.Stub { * The PDU starts with the SMSC address per TS 27.005 (+CMT:) */ public void newSms(int indicationType, byte[] pdu) { - mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS); SmsMessageBase smsb = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu); @@ -117,7 +119,7 @@ public class MessagingIndication extends IRadioMessagingIndication.Stub { * @param recordNumber Record number on the SIM */ public void newSmsOnSim(int indicationType, int recordNumber) { - mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM); @@ -133,7 +135,7 @@ public class MessagingIndication extends IRadioMessagingIndication.Stub { * The PDU starts with the SMSC address per TS 27.005 (+CMT:) */ public void newSmsStatusReport(int indicationType, byte[] pdu) { - mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT); @@ -149,7 +151,7 @@ public class MessagingIndication extends IRadioMessagingIndication.Stub { * @param indicationType Type of radio indication */ public void simSmsStorageFull(int indicationType) { - mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL); diff --git a/src/java/com/android/internal/telephony/MessagingResponse.java b/src/java/com/android/internal/telephony/MessagingResponse.java index 3dc1d1a965f61a0775cd2e78f5a29260c1865ea6..19211e15f5b18f5bf450486b9186d2eab20e08eb 100644 --- a/src/java/com/android/internal/telephony/MessagingResponse.java +++ b/src/java/com/android/internal/telephony/MessagingResponse.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING; + import android.hardware.radio.RadioError; import android.hardware.radio.RadioResponseInfo; import android.hardware.radio.messaging.IRadioMessagingResponse; @@ -36,7 +38,7 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { private void responseSms(RadioResponseInfo responseInfo, android.hardware.radio.messaging.SendSmsResult sms) { - RILRequest rr = mRil.processResponse(RIL.MESSAGING_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MESSAGING, responseInfo); if (rr != null) { long messageId = RIL.getOutgoingSmsMessageId(rr.mResult); @@ -62,35 +64,35 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void acknowledgeIncomingGsmSmsWithPduResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void acknowledgeLastIncomingCdmaSmsResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void acknowledgeLastIncomingGsmSmsResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void deleteSmsOnRuimResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void deleteSmsOnSimResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** @@ -99,7 +101,7 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { */ public void getCdmaBroadcastConfigResponse(RadioResponseInfo responseInfo, android.hardware.radio.messaging.CdmaBroadcastSmsConfigInfo[] configs) { - RILRequest rr = mRil.processResponse(RIL.MESSAGING_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MESSAGING, responseInfo); if (rr != null) { int[] ret; @@ -148,7 +150,7 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { */ public void getGsmBroadcastConfigResponse(RadioResponseInfo responseInfo, android.hardware.radio.messaging.GsmBroadcastSmsConfigInfo[] configs) { - RILRequest rr = mRil.processResponse(RIL.MESSAGING_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MESSAGING, responseInfo); if (rr != null) { ArrayList ret = new ArrayList<>(); @@ -168,14 +170,14 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { * @param smsc Short Message Service Center address on the device */ public void getSmscAddressResponse(RadioResponseInfo responseInfo, String smsc) { - RadioResponse.responseString(RIL.MESSAGING_SERVICE, mRil, responseInfo, smsc); + RadioResponse.responseString(HAL_SERVICE_MESSAGING, mRil, responseInfo, smsc); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void reportSmsMemoryStatusResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** @@ -227,35 +229,35 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCdmaBroadcastActivationResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCdmaBroadcastConfigResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setGsmBroadcastActivationResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setGsmBroadcastConfigResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setSmscAddressResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo); } /** @@ -263,7 +265,7 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { * @param index record index where the CDMA SMS message is stored */ public void writeSmsToRuimResponse(RadioResponseInfo responseInfo, int index) { - RadioResponse.responseInts(RIL.MESSAGING_SERVICE, mRil, responseInfo, index); + RadioResponse.responseInts(HAL_SERVICE_MESSAGING, mRil, responseInfo, index); } /** @@ -271,7 +273,7 @@ public class MessagingResponse extends IRadioMessagingResponse.Stub { * @param index record index where the message is stored */ public void writeSmsToSimResponse(RadioResponseInfo responseInfo, int index) { - RadioResponse.responseInts(RIL.MESSAGING_SERVICE, mRil, responseInfo, index); + RadioResponse.responseInts(HAL_SERVICE_MESSAGING, mRil, responseInfo, index); } @Override diff --git a/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java b/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java index 97495d6b0b405b0a86e361767b8c1f33d92c157b..dce65af8b43c1e1af0ab6318b19b17cb71bc6e64 100644 --- a/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java +++ b/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java @@ -240,7 +240,7 @@ public class MissedIncomingCallSmsFilter { private String[] splitCalls(String messageBody) { String[] messages = null; if (messageBody != null) { - messages = messageBody.split("\\n" + "\\n"); + messages = messageBody.split("(\\n|\\s\\n)" + "(\\n|\\s\\n)"); Rlog.d(TAG, "splitTheMultipleCalls no of calls = " + ((messages != null) ? messages.length : 0)); @@ -248,20 +248,6 @@ public class MissedIncomingCallSmsFilter { return messages; } - // Create phone account. The logic is copied from PhoneUtils.makePstnPhoneAccountHandle. - private PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { - SubscriptionManager subscriptionManager = - (SubscriptionManager) phone.getContext().getSystemService( - Context.TELEPHONY_SUBSCRIPTION_SERVICE); - UserHandle userHandle = subscriptionManager.getSubscriptionUserHandle(phone.getSubId()); - if (userHandle != null) { - return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, - String.valueOf(phone.getSubId()), userHandle); - } - return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, - String.valueOf(phone.getSubId())); - } - /** * Create the missed incoming call through TelecomManager. * @@ -288,4 +274,18 @@ public class MissedIncomingCallSmsFilter { tm.addNewIncomingCall(makePstnPhoneAccountHandle(mPhone), bundle); } } -} + + // Create phone account. The logic is copied from PhoneUtils.makePstnPhoneAccountHandle. + private PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { + SubscriptionManager subscriptionManager = + (SubscriptionManager) phone.getContext().getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + UserHandle userHandle = subscriptionManager.getSubscriptionUserHandle(phone.getSubId()); + if (userHandle != null) { + return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, + String.valueOf(phone.getSubId()), userHandle); + } + return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, + String.valueOf(phone.getSubId())); + } +} \ No newline at end of file diff --git a/src/java/com/android/internal/telephony/MockModem.java b/src/java/com/android/internal/telephony/MockModem.java index 4266a7518d50f1b170c149356d95a91c3fd84708..a20e74818fd7c8ea3afd1c455f371103d14f644a 100644 --- a/src/java/com/android/internal/telephony/MockModem.java +++ b/src/java/com/android/internal/telephony/MockModem.java @@ -16,6 +16,14 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_DATA; +import static android.telephony.TelephonyManager.HAL_SERVICE_IMS; +import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING; +import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; +import static android.telephony.TelephonyManager.HAL_SERVICE_SIM; +import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -34,6 +42,7 @@ public class MockModem { private static final String BIND_IRADIODATA = "android.telephony.mockmodem.iradiodata"; private static final String BIND_IRADIONETWORK = "android.telephony.mockmodem.iradionetwork"; private static final String BIND_IRADIOVOICE = "android.telephony.mockmodem.iradiovoice"; + private static final String BIND_IRADIOIMS = "android.telephony.mockmodem.iradioims"; private static final String BIND_IRADIOCONFIG = "android.telephony.mockmodem.iradioconfig"; private static final String PHONE_ID = "phone_id"; @@ -42,7 +51,7 @@ public class MockModem { static final int RADIOCONFIG_SERVICE = RIL.MAX_SERVICE_IDX + 1; static final int BINDER_RETRY_MILLIS = 3 * 100; - static final int BINDER_MAX_RETRY = 3; + static final int BINDER_MAX_RETRY = 10; private Context mContext; private String mServiceName; @@ -54,6 +63,7 @@ public class MockModem { private IBinder mDataBinder; private IBinder mNetworkBinder; private IBinder mVoiceBinder; + private IBinder mImsBinder; private IBinder mConfigBinder; private ServiceConnection mModemServiceConnection; private ServiceConnection mSimServiceConnection; @@ -61,9 +71,11 @@ public class MockModem { private ServiceConnection mDataServiceConnection; private ServiceConnection mNetworkServiceConnection; private ServiceConnection mVoiceServiceConnection; + private ServiceConnection mImsServiceConnection; private ServiceConnection mConfigServiceConnection; private byte mPhoneId; + private String mTag; MockModem(Context context, String serviceName) { this(context, serviceName, 0); @@ -71,6 +83,7 @@ public class MockModem { MockModem(Context context, String serviceName, int phoneId) { mPhoneId = (byte) phoneId; + mTag = TAG + "-" + mPhoneId; mContext = context; String[] componentInfo = serviceName.split("/", 2); mPackageName = componentInfo[0]; @@ -86,20 +99,22 @@ public class MockModem { @Override public void onServiceConnected(ComponentName name, IBinder binder) { - Rlog.d(TAG, "IRadio " + getModuleName(mService) + " - onServiceConnected"); + Rlog.d(mTag, "IRadio " + getModuleName(mService) + " - onServiceConnected"); - if (mService == RIL.MODEM_SERVICE) { + if (mService == HAL_SERVICE_MODEM) { mModemBinder = binder; - } else if (mService == RIL.SIM_SERVICE) { + } else if (mService == HAL_SERVICE_SIM) { mSimBinder = binder; - } else if (mService == RIL.MESSAGING_SERVICE) { + } else if (mService == HAL_SERVICE_MESSAGING) { mMessagingBinder = binder; - } else if (mService == RIL.DATA_SERVICE) { + } else if (mService == HAL_SERVICE_DATA) { mDataBinder = binder; - } else if (mService == RIL.NETWORK_SERVICE) { + } else if (mService == HAL_SERVICE_NETWORK) { mNetworkBinder = binder; - } else if (mService == RIL.VOICE_SERVICE) { + } else if (mService == HAL_SERVICE_VOICE) { mVoiceBinder = binder; + } else if (mService == HAL_SERVICE_IMS) { + mImsBinder = binder; } else if (mService == RADIOCONFIG_SERVICE) { mConfigBinder = binder; } @@ -107,20 +122,22 @@ public class MockModem { @Override public void onServiceDisconnected(ComponentName name) { - Rlog.d(TAG, "IRadio " + getModuleName(mService) + " - onServiceDisconnected"); + Rlog.d(mTag, "IRadio " + getModuleName(mService) + " - onServiceDisconnected"); - if (mService == RIL.MODEM_SERVICE) { + if (mService == HAL_SERVICE_MODEM) { mModemBinder = null; - } else if (mService == RIL.SIM_SERVICE) { + } else if (mService == HAL_SERVICE_SIM) { mSimBinder = null; - } else if (mService == RIL.MESSAGING_SERVICE) { + } else if (mService == HAL_SERVICE_MESSAGING) { mMessagingBinder = null; - } else if (mService == RIL.DATA_SERVICE) { + } else if (mService == HAL_SERVICE_DATA) { mDataBinder = null; - } else if (mService == RIL.NETWORK_SERVICE) { + } else if (mService == HAL_SERVICE_NETWORK) { mNetworkBinder = null; - } else if (mService == RIL.VOICE_SERVICE) { + } else if (mService == HAL_SERVICE_VOICE) { mVoiceBinder = null; + } else if (mService == HAL_SERVICE_IMS) { + mImsBinder = null; } else if (mService == RADIOCONFIG_SERVICE) { mConfigBinder = null; } @@ -138,7 +155,7 @@ public class MockModem { Intent intent = new Intent(); intent.setComponent(new ComponentName(mPackageName, mServiceName)); - intent.setAction(actionName); + intent.setAction(actionName + phoneId); intent.putExtra(PHONE_ID, phoneId); status = mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); @@ -148,18 +165,20 @@ public class MockModem { /** waitForBinder */ public IBinder getServiceBinder(int service) { switch (service) { - case RIL.MODEM_SERVICE: + case HAL_SERVICE_MODEM: return mModemBinder; - case RIL.SIM_SERVICE: + case HAL_SERVICE_SIM: return mSimBinder; - case RIL.MESSAGING_SERVICE: + case HAL_SERVICE_MESSAGING: return mMessagingBinder; - case RIL.DATA_SERVICE: + case HAL_SERVICE_DATA: return mDataBinder; - case RIL.NETWORK_SERVICE: + case HAL_SERVICE_NETWORK: return mNetworkBinder; - case RIL.VOICE_SERVICE: + case HAL_SERVICE_VOICE: return mVoiceBinder; + case HAL_SERVICE_IMS: + return mImsBinder; case RADIOCONFIG_SERVICE: return mConfigBinder; default: @@ -183,95 +202,109 @@ public class MockModem { boolean status = bindModuleToMockModemService(BIND_IRADIOCONFIG, mConfigServiceConnection); if (!status) { - Rlog.d(TAG, "IRadio Config bind fail"); + Rlog.d(mTag, "IRadio Config bind fail"); mConfigServiceConnection = null; } } else { - Rlog.d(TAG, "IRadio Config is bound"); + Rlog.d(mTag, "IRadio Config is bound"); } - } else if (service == RIL.MODEM_SERVICE) { + } else if (service == HAL_SERVICE_MODEM) { if (mModemBinder == null) { - mModemServiceConnection = new MockModemConnection(RIL.MODEM_SERVICE); + mModemServiceConnection = new MockModemConnection(HAL_SERVICE_MODEM); boolean status = bindModuleToMockModemService( mPhoneId, BIND_IRADIOMODEM, mModemServiceConnection); if (!status) { - Rlog.d(TAG, "IRadio Modem bind fail"); + Rlog.d(mTag, "IRadio Modem bind fail"); mModemServiceConnection = null; } } else { - Rlog.d(TAG, "IRadio Modem is bound"); + Rlog.d(mTag, "IRadio Modem is bound"); } - } else if (service == RIL.SIM_SERVICE) { + } else if (service == HAL_SERVICE_SIM) { if (mSimBinder == null) { - mSimServiceConnection = new MockModemConnection(RIL.SIM_SERVICE); + mSimServiceConnection = new MockModemConnection(HAL_SERVICE_SIM); boolean status = bindModuleToMockModemService( mPhoneId, BIND_IRADIOSIM, mSimServiceConnection); if (!status) { - Rlog.d(TAG, "IRadio Sim bind fail"); + Rlog.d(mTag, "IRadio Sim bind fail"); mSimServiceConnection = null; } } else { - Rlog.d(TAG, "IRadio Sim is bound"); + Rlog.d(mTag, "IRadio Sim is bound"); } - } else if (service == RIL.MESSAGING_SERVICE) { + } else if (service == HAL_SERVICE_MESSAGING) { if (mMessagingBinder == null) { - mMessagingServiceConnection = new MockModemConnection(RIL.MESSAGING_SERVICE); + mMessagingServiceConnection = new MockModemConnection(HAL_SERVICE_MESSAGING); boolean status = bindModuleToMockModemService( mPhoneId, BIND_IRADIOMESSAGING, mMessagingServiceConnection); if (!status) { - Rlog.d(TAG, "IRadio Messaging bind fail"); + Rlog.d(mTag, "IRadio Messaging bind fail"); mMessagingServiceConnection = null; } } else { - Rlog.d(TAG, "IRadio Messaging is bound"); + Rlog.d(mTag, "IRadio Messaging is bound"); } - } else if (service == RIL.DATA_SERVICE) { + } else if (service == HAL_SERVICE_DATA) { if (mDataBinder == null) { - mDataServiceConnection = new MockModemConnection(RIL.DATA_SERVICE); + mDataServiceConnection = new MockModemConnection(HAL_SERVICE_DATA); boolean status = bindModuleToMockModemService( mPhoneId, BIND_IRADIODATA, mDataServiceConnection); if (!status) { - Rlog.d(TAG, "IRadio Data bind fail"); + Rlog.d(mTag, "IRadio Data bind fail"); mDataServiceConnection = null; } } else { - Rlog.d(TAG, "IRadio Data is bound"); + Rlog.d(mTag, "IRadio Data is bound"); } - } else if (service == RIL.NETWORK_SERVICE) { + } else if (service == HAL_SERVICE_NETWORK) { if (mNetworkBinder == null) { - mNetworkServiceConnection = new MockModemConnection(RIL.NETWORK_SERVICE); + mNetworkServiceConnection = new MockModemConnection(HAL_SERVICE_NETWORK); boolean status = bindModuleToMockModemService( mPhoneId, BIND_IRADIONETWORK, mNetworkServiceConnection); if (!status) { - Rlog.d(TAG, "IRadio Network bind fail"); + Rlog.d(mTag, "IRadio Network bind fail"); mNetworkServiceConnection = null; } } else { - Rlog.d(TAG, "IRadio Network is bound"); + Rlog.d(mTag, "IRadio Network is bound"); } - } else if (service == RIL.VOICE_SERVICE) { + } else if (service == HAL_SERVICE_VOICE) { if (mVoiceBinder == null) { - mVoiceServiceConnection = new MockModemConnection(RIL.VOICE_SERVICE); + mVoiceServiceConnection = new MockModemConnection(HAL_SERVICE_VOICE); boolean status = bindModuleToMockModemService( mPhoneId, BIND_IRADIOVOICE, mVoiceServiceConnection); if (!status) { - Rlog.d(TAG, "IRadio Voice bind fail"); + Rlog.d(mTag, "IRadio Voice bind fail"); mVoiceServiceConnection = null; } } else { - Rlog.d(TAG, "IRadio Voice is bound"); + Rlog.d(mTag, "IRadio Voice is bound"); + } + } else if (service == HAL_SERVICE_IMS) { + if (mImsBinder == null) { + mImsServiceConnection = new MockModemConnection(HAL_SERVICE_IMS); + + boolean status = + bindModuleToMockModemService( + mPhoneId, BIND_IRADIOIMS, mImsServiceConnection); + if (!status) { + Rlog.d(TAG, "IRadio Ims bind fail"); + mImsServiceConnection = null; + } + } else { + Rlog.d(TAG, "IRadio Ims is bound"); } } } @@ -284,49 +317,56 @@ public class MockModem { mContext.unbindService(mConfigServiceConnection); mConfigServiceConnection = null; mConfigBinder = null; - Rlog.d(TAG, "unbind IRadio Config"); + Rlog.d(mTag, "unbind IRadio Config"); } - } else if (service == RIL.MODEM_SERVICE) { + } else if (service == HAL_SERVICE_MODEM) { if (mModemServiceConnection != null) { mContext.unbindService(mModemServiceConnection); mModemServiceConnection = null; mModemBinder = null; - Rlog.d(TAG, "unbind IRadio Modem"); + Rlog.d(mTag, "unbind IRadio Modem"); } - } else if (service == RIL.SIM_SERVICE) { + } else if (service == HAL_SERVICE_SIM) { if (mSimServiceConnection != null) { mContext.unbindService(mSimServiceConnection); mSimServiceConnection = null; mSimBinder = null; - Rlog.d(TAG, "unbind IRadio Sim"); + Rlog.d(mTag, "unbind IRadio Sim"); } - } else if (service == RIL.MESSAGING_SERVICE) { + } else if (service == HAL_SERVICE_MESSAGING) { if (mMessagingServiceConnection != null) { mContext.unbindService(mMessagingServiceConnection); mMessagingServiceConnection = null; mMessagingBinder = null; - Rlog.d(TAG, "unbind IRadio Messaging"); + Rlog.d(mTag, "unbind IRadio Messaging"); } - } else if (service == RIL.DATA_SERVICE) { + } else if (service == HAL_SERVICE_DATA) { if (mDataServiceConnection != null) { mContext.unbindService(mDataServiceConnection); mDataServiceConnection = null; mDataBinder = null; - Rlog.d(TAG, "unbind IRadio Data"); + Rlog.d(mTag, "unbind IRadio Data"); } - } else if (service == RIL.NETWORK_SERVICE) { + } else if (service == HAL_SERVICE_NETWORK) { if (mNetworkServiceConnection != null) { mContext.unbindService(mNetworkServiceConnection); mNetworkServiceConnection = null; mNetworkBinder = null; - Rlog.d(TAG, "unbind IRadio Network"); + Rlog.d(mTag, "unbind IRadio Network"); } - } else if (service == RIL.VOICE_SERVICE) { + } else if (service == HAL_SERVICE_VOICE) { if (mVoiceServiceConnection != null) { mContext.unbindService(mVoiceServiceConnection); mVoiceServiceConnection = null; mVoiceBinder = null; - Rlog.d(TAG, "unbind IRadio Voice"); + Rlog.d(mTag, "unbind IRadio Voice"); + } + } else if (service == HAL_SERVICE_IMS) { + if (mImsServiceConnection != null) { + mContext.unbindService(mImsServiceConnection); + mImsServiceConnection = null; + mImsBinder = null; + Rlog.d(TAG, "unbind IRadio Ims"); } } } @@ -337,18 +377,20 @@ public class MockModem { private String getModuleName(int service) { switch (service) { - case RIL.MODEM_SERVICE: + case HAL_SERVICE_MODEM: return "modem"; - case RIL.SIM_SERVICE: + case HAL_SERVICE_SIM: return "sim"; - case RIL.MESSAGING_SERVICE: + case HAL_SERVICE_MESSAGING: return "messaging"; - case RIL.DATA_SERVICE: + case HAL_SERVICE_DATA: return "data"; - case RIL.NETWORK_SERVICE: + case HAL_SERVICE_NETWORK: return "network"; - case RIL.VOICE_SERVICE: + case HAL_SERVICE_VOICE: return "voice"; + case HAL_SERVICE_IMS: + return "ims"; case RADIOCONFIG_SERVICE: return "config"; default: diff --git a/src/java/com/android/internal/telephony/ModemIndication.java b/src/java/com/android/internal/telephony/ModemIndication.java index d05e0f6730c7ba1582adf87bdc83001b37693b35..0ee40bb6ffad7d2a948ce3053198a2b22f2623f8 100644 --- a/src/java/com/android/internal/telephony/ModemIndication.java +++ b/src/java/com/android/internal/telephony/ModemIndication.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM; + import static com.android.internal.telephony.RILConstants.RIL_UNSOL_HARDWARE_CONFIG_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_MODEM_RESTART; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RADIO_CAPABILITY; @@ -44,7 +46,7 @@ public class ModemIndication extends IRadioModemIndication.Stub { */ public void hardwareConfigChanged(int indicationType, android.hardware.radio.modem.HardwareConfig[] configs) { - mRil.processIndication(RIL.MODEM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MODEM, indicationType); ArrayList response = RILUtils.convertHalHardwareConfigList(configs); @@ -62,7 +64,7 @@ public class ModemIndication extends IRadioModemIndication.Stub { * restart" that explains the cause of the modem restart */ public void modemReset(int indicationType, String reason) { - mRil.processIndication(RIL.MODEM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MODEM, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason); @@ -78,7 +80,7 @@ public class ModemIndication extends IRadioModemIndication.Stub { */ public void radioCapabilityIndication(int indicationType, android.hardware.radio.modem.RadioCapability radioCapability) { - mRil.processIndication(RIL.MODEM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MODEM, indicationType); RadioCapability response = RILUtils.convertHalRadioCapability(radioCapability, mRil); @@ -94,7 +96,7 @@ public class ModemIndication extends IRadioModemIndication.Stub { * @param radioState Current radio state */ public void radioStateChanged(int indicationType, int radioState) { - mRil.processIndication(RIL.MODEM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MODEM, indicationType); int state = RILUtils.convertHalRadioState(radioState); if (mRil.isLogOrTrace()) { @@ -110,7 +112,7 @@ public class ModemIndication extends IRadioModemIndication.Stub { * @param indicationType Type of radio indication */ public void rilConnected(int indicationType) { - mRil.processIndication(RIL.MODEM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_MODEM, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED); diff --git a/src/java/com/android/internal/telephony/ModemResponse.java b/src/java/com/android/internal/telephony/ModemResponse.java index 6e44ddc76e242e6b7adb1d4365b7be4843de6d0b..bd04d16ebc08fed5e682e58ede5b16afc72a06d3 100644 --- a/src/java/com/android/internal/telephony/ModemResponse.java +++ b/src/java/com/android/internal/telephony/ModemResponse.java @@ -16,9 +16,12 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM; + import android.hardware.radio.RadioError; import android.hardware.radio.RadioResponseInfo; import android.hardware.radio.modem.IRadioModemResponse; +import android.hardware.radio.modem.ImeiInfo; import android.os.SystemClock; import android.telephony.ActivityStatsTechSpecificInfo; import android.telephony.AnomalyReporter; @@ -51,7 +54,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { * @param responseInfo Response info struct containing response type, serial number and error. */ public void enableModemResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo); } /** @@ -59,7 +62,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { * @param version String containing version string for log reporting */ public void getBasebandVersionResponse(RadioResponseInfo responseInfo, String version) { - RadioResponse.responseString(RIL.MODEM_SERVICE, mRil, responseInfo, version); + RadioResponse.responseString(HAL_SERVICE_MODEM, mRil, responseInfo, version); } /** @@ -72,7 +75,21 @@ public class ModemResponse extends IRadioModemResponse.Stub { public void getDeviceIdentityResponse(RadioResponseInfo responseInfo, String imei, String imeisv, String esn, String meid) { RadioResponse.responseStrings( - RIL.MODEM_SERVICE, mRil, responseInfo, imei, imeisv, esn, meid); + HAL_SERVICE_MODEM, mRil, responseInfo, imei, imeisv, esn, meid); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error + * @param imeiInfo object containing ImeiType, device IMEI and IMEISV + */ + public void getImeiResponse(RadioResponseInfo responseInfo, ImeiInfo imeiInfo) { + RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo); + if (rr != null) { + if (responseInfo.error == RadioError.NONE) { + RadioResponse.sendMessageResponse(rr.mResult, imeiInfo); + } + mRil.processResponseDone(rr, responseInfo, imeiInfo); + } } /** @@ -81,7 +98,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { */ public void getHardwareConfigResponse(RadioResponseInfo responseInfo, android.hardware.radio.modem.HardwareConfig[] config) { - RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo); if (rr != null) { ArrayList ret = RILUtils.convertHalHardwareConfigList(config); @@ -98,7 +115,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { */ public void getModemActivityInfoResponse(RadioResponseInfo responseInfo, android.hardware.radio.modem.ActivityStatsInfo activityInfo) { - RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo); if (rr != null) { ModemActivityInfo ret = null; @@ -141,7 +158,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { * @param isEnabled whether the modem stack is enabled. */ public void getModemStackStatusResponse(RadioResponseInfo responseInfo, boolean isEnabled) { - RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { @@ -157,7 +174,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { */ public void getRadioCapabilityResponse(RadioResponseInfo responseInfo, android.hardware.radio.modem.RadioCapability radioCapability) { - RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo); if (rr != null) { RadioCapability ret = RILUtils.convertHalRadioCapability(radioCapability, mRil); @@ -179,42 +196,42 @@ public class ModemResponse extends IRadioModemResponse.Stub { * @param result String containing the contents of the NV item */ public void nvReadItemResponse(RadioResponseInfo responseInfo, String result) { - RadioResponse.responseString(RIL.MODEM_SERVICE, mRil, responseInfo, result); + RadioResponse.responseString(HAL_SERVICE_MODEM, mRil, responseInfo, result); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void nvResetConfigResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void nvWriteCdmaPrlResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void nvWriteItemResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void requestShutdownResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void sendDeviceStateResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo); } /** @@ -223,7 +240,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { */ public void setRadioCapabilityResponse(RadioResponseInfo responseInfo, android.hardware.radio.modem.RadioCapability radioCapability) { - RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo); if (rr != null) { RadioCapability ret = RILUtils.convertHalRadioCapability(radioCapability, mRil); @@ -238,7 +255,7 @@ public class ModemResponse extends IRadioModemResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error. */ public void setRadioPowerResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo); mRil.mLastRadioPowerResult = responseInfo.error; if (responseInfo.error == RadioError.RF_HARDWARE_ISSUE) { AnomalyReporter.reportAnomaly( diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java index 7e87b5fa2a3a07fa4e0ba3c1f1f9c7f68b529256..0acae4b56db766e0a7c4477d8fe08cb31d238044 100644 --- a/src/java/com/android/internal/telephony/MultiSimSettingController.java +++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java @@ -120,7 +120,6 @@ public class MultiSimSettingController extends Handler { private static final int PRIMARY_SUB_REMOVED_IN_GROUP = 7; protected final Context mContext; - protected final SubscriptionController mSubController; private final SubscriptionManagerService mSubscriptionManagerService; // Keep a record of active primary (non-opportunistic) subscription list. @@ -129,12 +128,7 @@ public class MultiSimSettingController extends Handler { /** The singleton instance. */ protected static MultiSimSettingController sInstance = null; - // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. The reason of keeping - // a local variable instead of calling SubscriptionInfoUpdater#isSubInfoInitialized is, there - // might be a race condition that we receive EVENT_SUBSCRIPTION_INFO_CHANGED first, then - // EVENT_ALL_SUBSCRIPTIONS_LOADED. And calling SubscriptionInfoUpdater#isSubInfoInitialized - // will make us handle EVENT_SUBSCRIPTION_INFO_CHANGED unexpectedly and causing us to believe - // the SIMs are newly inserted instead of being initialized. + // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. private boolean mSubInfoInitialized = false; // mInitialHandling is to make sure we don't always ask user to re-select data SIM after reboot. @@ -206,10 +200,10 @@ public class MultiSimSettingController extends Handler { /** * Init instance of MultiSimSettingController. */ - public static MultiSimSettingController init(Context context, SubscriptionController sc) { + public static MultiSimSettingController init(Context context) { synchronized (MultiSimSettingController.class) { if (sInstance == null) { - sInstance = new MultiSimSettingController(context, sc); + sInstance = new MultiSimSettingController(context); } else { Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); } @@ -218,9 +212,8 @@ public class MultiSimSettingController extends Handler { } @VisibleForTesting - public MultiSimSettingController(Context context, SubscriptionController sc) { + public MultiSimSettingController(Context context) { mContext = context; - mSubController = sc; mSubscriptionManagerService = SubscriptionManagerService.getInstance(); // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change. @@ -346,26 +339,15 @@ public class MultiSimSettingController extends Handler { // Make sure MOBILE_DATA of subscriptions in same group are synced. setUserDataEnabledForGroup(subId, enable); - SubscriptionInfo subInfo = null; - int defaultDataSubId; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId); - defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); - } else { - subInfo = mSubController.getSubscriptionInfo(subId); - defaultDataSubId = mSubController.getDefaultDataSubId(); - } + SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId); + int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); // If user is enabling a non-default non-opportunistic subscription, make it default. if (defaultDataSubId != subId && subInfo != null && !subInfo.isOpportunistic() && enable && subInfo.isActive() && setDefaultData) { android.provider.Settings.Global.putInt(mContext.getContentResolver(), SETTING_USER_PREF_DATA_SUB, subId); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.setDefaultDataSubId(subId); - } else { - mSubController.setDefaultDataSubId(subId); - } + mSubscriptionManagerService.setDefaultDataSubId(subId); } } @@ -376,13 +358,8 @@ public class MultiSimSettingController extends Handler { if (DBG) log("onRoamingDataEnabled"); setRoamingDataEnabledForGroup(subId, enable); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - // Also inform SubscriptionController as it keeps another copy of user setting. - mSubscriptionManagerService.setDataRoaming(enable ? 1 : 0, subId); - } else { - // Also inform SubscriptionController as it keeps another copy of user setting. - mSubController.setDataRoaming(enable ? 1 : 0, subId); - } + // Also inform SubscriptionManagerService as it keeps another copy of user setting. + mSubscriptionManagerService.setDataRoaming(enable ? 1 : 0, subId); } /** @@ -457,12 +434,7 @@ public class MultiSimSettingController extends Handler { */ @VisibleForTesting public boolean isCarrierConfigLoadedForAllSub() { - int[] activeSubIds; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - activeSubIds = mSubscriptionManagerService.getActiveSubIdList(false); - } else { - activeSubIds = mSubController.getActiveSubIdList(false); - } + int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(false); for (int activeSubId : activeSubIds) { boolean isLoaded = false; for (int configLoadedSub : mCarrierConfigLoadedSubIds) { @@ -529,17 +501,9 @@ public class MultiSimSettingController extends Handler { private void onSubscriptionGroupChanged(ParcelUuid groupUuid) { if (DBG) log("onSubscriptionGroupChanged"); - List infoList; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - infoList = mSubscriptionManagerService.getSubscriptionsInGroup( - groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag()); - if (infoList == null || infoList.isEmpty()) return; - - } else { - infoList = mSubController.getSubscriptionsInGroup( - groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag()); - if (infoList == null || infoList.isEmpty()) return; - } + List infoList = mSubscriptionManagerService.getSubscriptionsInGroup( + groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag()); + if (infoList == null || infoList.isEmpty()) return; // Get a reference subscription to copy settings from. // TODO: the reference sub should be passed in from external caller. @@ -564,14 +528,9 @@ public class MultiSimSettingController extends Handler { mContext, Settings.Global.MOBILE_DATA, INVALID_SUBSCRIPTION_ID, enable); } boolean setDefaultData = true; - List activeSubList; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - activeSubList = mSubscriptionManagerService.getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - } else { - activeSubList = mSubController.getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - } + List activeSubList = mSubscriptionManagerService + .getActiveSubscriptionInfoList(mContext.getOpPackageName(), + mContext.getAttributionTag()); for (SubscriptionInfo activeInfo : activeSubList) { if (!(groupUuid.equals(activeInfo.getGroupUuid()))) { // Do not set refSubId as defaultDataSubId if there are other active @@ -594,12 +553,7 @@ public class MultiSimSettingController extends Handler { onRoamingDataEnabled(refSubId, enable); } - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.syncGroupedSetting(refSubId); - } else { - // Sync settings in subscription database.. - mSubController.syncGroupedSetting(refSubId); - } + mSubscriptionManagerService.syncGroupedSetting(refSubId); } /** @@ -621,34 +575,20 @@ public class MultiSimSettingController extends Handler { if (!isReadyToReevaluate()) return; - List activeSubInfos; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - activeSubInfos = mSubscriptionManagerService.getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - - if (ArrayUtils.isEmpty(activeSubInfos)) { - mPrimarySubList.clear(); - if (DBG) log("updateDefaults: No active sub. Setting default to INVALID sub."); - mSubscriptionManagerService.setDefaultDataSubId( - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mSubscriptionManagerService.setDefaultVoiceSubId( - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mSubscriptionManagerService.setDefaultSmsSubId( - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - return; - } - } else { - activeSubInfos = mSubController.getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - - if (ArrayUtils.isEmpty(activeSubInfos)) { - mPrimarySubList.clear(); - if (DBG) log("updateDefaultValues: No active sub. Setting default to INVALID sub."); - mSubController.setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mSubController.setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mSubController.setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); - return; - } + List activeSubInfos = mSubscriptionManagerService + .getActiveSubscriptionInfoList(mContext.getOpPackageName(), + mContext.getAttributionTag()); + + if (ArrayUtils.isEmpty(activeSubInfos)) { + mPrimarySubList.clear(); + if (DBG) log("updateDefaults: No active sub. Setting default to INVALID sub."); + mSubscriptionManagerService.setDefaultDataSubId( + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mSubscriptionManagerService.setDefaultVoiceSubId( + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mSubscriptionManagerService.setDefaultSmsSubId( + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + return; } int change = updatePrimarySubListAndGetChangeType(activeSubInfos); @@ -665,15 +605,9 @@ public class MultiSimSettingController extends Handler { .getActiveModemCount() == 1)) { int subId = mPrimarySubList.get(0); if (DBG) log("updateDefaultValues: to only primary sub " + subId); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.setDefaultDataSubId(subId); - mSubscriptionManagerService.setDefaultVoiceSubId(subId); - mSubscriptionManagerService.setDefaultSmsSubId(subId); - } else { - mSubController.setDefaultDataSubId(subId); - mSubController.setDefaultVoiceSubId(subId); - mSubController.setDefaultSmsSubId(subId); - } + mSubscriptionManagerService.setDefaultDataSubId(subId); + mSubscriptionManagerService.setDefaultVoiceSubId(subId); + mSubscriptionManagerService.setDefaultSmsSubId(subId); sendDefaultSubConfirmedNotification(subId); return; } @@ -682,45 +616,24 @@ public class MultiSimSettingController extends Handler { boolean dataSelected, voiceSelected, smsSelected; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - // Update default data subscription. - if (DBG) log("updateDefaultValues: Update default data subscription"); - dataSelected = updateDefaultValue(mPrimarySubList, - mSubscriptionManagerService.getDefaultDataSubId(), - mSubscriptionManagerService::setDefaultDataSubId); - - // Update default voice subscription. - if (DBG) log("updateDefaultValues: Update default voice subscription"); - voiceSelected = updateDefaultValue(mPrimarySubList, - mSubscriptionManagerService.getDefaultVoiceSubId(), - mSubscriptionManagerService::setDefaultVoiceSubId); - - // Update default sms subscription. - if (DBG) log("updateDefaultValues: Update default sms subscription"); - smsSelected = updateDefaultValue(mPrimarySubList, - mSubscriptionManagerService.getDefaultSmsSubId(), - mSubscriptionManagerService::setDefaultSmsSubId, - mIsAskEverytimeSupportedForSms); - } else { - // Update default data subscription. - if (DBG) log("updateDefaultValues: Update default data subscription"); - dataSelected = updateDefaultValue(mPrimarySubList, - mSubController.getDefaultDataSubId(), - mSubController::setDefaultDataSubId); - - // Update default voice subscription. - if (DBG) log("updateDefaultValues: Update default voice subscription"); - voiceSelected = updateDefaultValue(mPrimarySubList, - mSubController.getDefaultVoiceSubId(), - mSubController::setDefaultVoiceSubId); - - // Update default sms subscription. - if (DBG) log("updateDefaultValues: Update default sms subscription"); - smsSelected = updateDefaultValue(mPrimarySubList, - mSubController.getDefaultSmsSubId(), - mSubController::setDefaultSmsSubId, - mIsAskEverytimeSupportedForSms); - } + // Update default data subscription. + if (DBG) log("updateDefaultValues: Update default data subscription"); + dataSelected = updateDefaultValue(mPrimarySubList, + mSubscriptionManagerService.getDefaultDataSubId(), + mSubscriptionManagerService::setDefaultDataSubId); + + // Update default voice subscription. + if (DBG) log("updateDefaultValues: Update default voice subscription"); + voiceSelected = updateDefaultValue(mPrimarySubList, + mSubscriptionManagerService.getDefaultVoiceSubId(), + mSubscriptionManagerService::setDefaultVoiceSubId); + + // Update default sms subscription. + if (DBG) log("updateDefaultValues: Update default sms subscription"); + smsSelected = updateDefaultValue(mPrimarySubList, + mSubscriptionManagerService.getDefaultSmsSubId(), + mSubscriptionManagerService::setDefaultSmsSubId, + mIsAskEverytimeSupportedForSms); boolean autoFallbackEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_voice_data_sms_auto_fallback); @@ -771,12 +684,7 @@ public class MultiSimSettingController extends Handler { // any previous primary subscription becomes inactive, we consider it for (int subId : prevPrimarySubList) { if (mPrimarySubList.contains(subId)) continue; - SubscriptionInfo subInfo = null; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId); - } else { - subInfo = mSubController.getSubscriptionInfo(subId); - } + SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId); if (subInfo == null || !subInfo.isActive()) { for (int currentSubId : mPrimarySubList) { @@ -890,16 +798,10 @@ public class MultiSimSettingController extends Handler { if (phone != null && phone.isCdmaSubscriptionAppPresent()) { cdmaPhoneCount++; String simName = null; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(subId); - if (subInfo != null) { - simName = subInfo.getDisplayName(); - } - } else { - simName = mSubController.getActiveSubscriptionInfo( - subId, mContext.getOpPackageName(), mContext.getAttributionTag()) - .getDisplayName().toString(); + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + if (subInfo != null) { + simName = subInfo.getDisplayName(); } if (TextUtils.isEmpty(simName)) { // Fall back to carrier name. @@ -925,22 +827,12 @@ public class MultiSimSettingController extends Handler { protected void disableDataForNonDefaultNonOpportunisticSubscriptions() { if (!isReadyToReevaluate()) return; - int defaultDataSub; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - defaultDataSub = mSubscriptionManagerService.getDefaultDataSubId(); - } else { - defaultDataSub = mSubController.getDefaultDataSubId(); - } + int defaultDataSub = mSubscriptionManagerService.getDefaultDataSubId(); for (Phone phone : PhoneFactory.getPhones()) { - boolean isOpportunistic; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(phone.getSubId()); - isOpportunistic = subInfo != null && subInfo.isOpportunistic(); - } else { - isOpportunistic = mSubController.isOpportunistic(phone.getSubId()); - } + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(phone.getSubId()); + boolean isOpportunistic = subInfo != null && subInfo.isOpportunistic(); if (phone.getSubId() != defaultDataSub && SubscriptionManager.isValidSubscriptionId(phone.getSubId()) && !isOpportunistic @@ -959,19 +851,13 @@ public class MultiSimSettingController extends Handler { || !SubscriptionManager.isUsableSubscriptionId(subId2)) return false; if (subId1 == subId2) return true; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo1 = - mSubscriptionManagerService.getSubscriptionInfoInternal(subId1); - SubscriptionInfoInternal subInfo2 = - mSubscriptionManagerService.getSubscriptionInfoInternal(subId2); - return subInfo1 != null && subInfo2 != null - && !TextUtils.isEmpty(subInfo1.getGroupUuid()) - && subInfo1.getGroupUuid().equals(subInfo2.getGroupUuid()); - } else { - ParcelUuid groupUuid1 = mSubController.getGroupUuid(subId1); - ParcelUuid groupUuid2 = mSubController.getGroupUuid(subId2); - return groupUuid1 != null && groupUuid1.equals(groupUuid2); - } + SubscriptionInfoInternal subInfo1 = + mSubscriptionManagerService.getSubscriptionInfoInternal(subId1); + SubscriptionInfoInternal subInfo2 = + mSubscriptionManagerService.getSubscriptionInfoInternal(subId2); + return subInfo1 != null && subInfo2 != null + && !TextUtils.isEmpty(subInfo1.getGroupUuid()) + && subInfo1.getGroupUuid().equals(subInfo2.getGroupUuid()); } /** @@ -981,17 +867,11 @@ public class MultiSimSettingController extends Handler { protected void setUserDataEnabledForGroup(int subId, boolean enable) { log("setUserDataEnabledForGroup subId " + subId + " enable " + enable); List infoList = null; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(subId); - if (subInfo != null && !subInfo.getGroupUuid().isEmpty()) { - infoList = mSubscriptionManagerService.getSubscriptionsInGroup( - ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(), - mContext.getAttributionTag()); - } - } else { - infoList = mSubController.getSubscriptionsInGroup( - mSubController.getGroupUuid(subId), mContext.getOpPackageName(), + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + if (subInfo != null && !subInfo.getGroupUuid().isEmpty()) { + infoList = mSubscriptionManagerService.getSubscriptionsInGroup( + ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(), mContext.getAttributionTag()); } @@ -1002,13 +882,8 @@ public class MultiSimSettingController extends Handler { // TODO: simplify when setUserDataEnabled becomes singleton if (info.isActive()) { // For active subscription, call setUserDataEnabled through DataSettingsManager. - Phone phone; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - phone = PhoneFactory.getPhone(mSubscriptionManagerService - .getPhoneId(currentSubId)); - } else { - phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId)); - } + Phone phone = PhoneFactory.getPhone(mSubscriptionManagerService + .getPhoneId(currentSubId)); // If enable is true and it's not opportunistic subscription, we don't enable it, // as there can't be two if (phone != null) { @@ -1029,19 +904,12 @@ public class MultiSimSettingController extends Handler { * are synced. */ private void setRoamingDataEnabledForGroup(int subId, boolean enable) { - List infoList; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(subId); - if (subInfo == null || subInfo.getGroupUuid().isEmpty()) return; - infoList = SubscriptionManagerService.getInstance().getSubscriptionsInGroup( - ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(), - mContext.getAttributionTag()); - } else { - infoList = SubscriptionController.getInstance().getSubscriptionsInGroup( - mSubController.getGroupUuid(subId), mContext.getOpPackageName(), - mContext.getAttributionTag()); - } + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + if (subInfo == null || subInfo.getGroupUuid().isEmpty()) return; + List infoList = SubscriptionManagerService.getInstance() + .getSubscriptionsInGroup(ParcelUuid.fromString(subInfo.getGroupUuid()), + mContext.getOpPackageName(), mContext.getAttributionTag()); if (infoList == null) return; for (SubscriptionInfo info : infoList) { @@ -1094,19 +962,10 @@ public class MultiSimSettingController extends Handler { // subscription gets deactivated or removed, we need to automatically disable the grouped // opportunistic subscription, which will be marked isGroupDisabled as true by SubController. private void deactivateGroupedOpportunisticSubscriptionIfNeeded() { - if (!SubscriptionInfoUpdater.isSubInfoInitialized()) return; - - List opptSubList; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - opptSubList = mSubscriptionManagerService.getAllSubInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()).stream() - .filter(SubscriptionInfo::isOpportunistic) - .collect(Collectors.toList()); - - } else { - opptSubList = mSubController.getOpportunisticSubscriptions( - mContext.getOpPackageName(), mContext.getAttributionTag()); - } + List opptSubList = mSubscriptionManagerService.getAllSubInfoList( + mContext.getOpPackageName(), mContext.getAttributionTag()).stream() + .filter(SubscriptionInfo::isOpportunistic) + .collect(Collectors.toList()); if (ArrayUtils.isEmpty(opptSubList)) return; @@ -1142,101 +1001,54 @@ public class MultiSimSettingController extends Handler { // would be selected as preferred voice/data/sms SIM. private void updateUserPreferences(List primarySubList, boolean dataSelected, boolean voiceSelected, boolean smsSelected) { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - - // In Single SIM case or if there are no activated subs available, no need to update. - // EXIT. - if ((primarySubList.isEmpty()) || (mSubscriptionManagerService - .getActiveSubInfoCountMax() == 1)) { - return; - } - - if (!isRadioAvailableOnAllSubs()) { - log("Radio is in Invalid state, Ignore Updating User Preference!!!"); - return; - } - final int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); - - if (DBG) { - log("updateUserPreferences: dds = " + defaultDataSubId + " voice = " - + mSubscriptionManagerService.getDefaultVoiceSubId() - + " sms = " + mSubscriptionManagerService.getDefaultSmsSubId()); - } - - int autoDefaultSubId = primarySubList.get(0); - - if ((primarySubList.size() == 1) && !smsSelected) { - mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId); - } - - if ((primarySubList.size() == 1) && !voiceSelected) { - mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId); - } - - int userPrefDataSubId = getUserPrefDataSubIdFromDB(); - - log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId - + " next active subId " + autoDefaultSubId); - - // If earlier user selected DDS is now available, set that as DDS subId. - if (primarySubList.contains(userPrefDataSubId) - && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId) - && (defaultDataSubId != userPrefDataSubId)) { - mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId); - } else if (!dataSelected) { - mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId); - } - - if (DBG) { - log("updateUserPreferences: after dds = " - + mSubscriptionManagerService.getDefaultDataSubId() + " voice = " - + mSubscriptionManagerService.getDefaultVoiceSubId() + " sms = " - + mSubscriptionManagerService.getDefaultSmsSubId()); - } + // In Single SIM case or if there are no activated subs available, no need to update. + // EXIT. + if ((primarySubList.isEmpty()) || (mSubscriptionManagerService + .getActiveSubInfoCountMax() == 1)) { return; } - // In Single SIM case or if there are no activated subs available, no need to update. EXIT. - if ((primarySubList.isEmpty()) || (mSubController.getActiveSubInfoCountMax() == 1)) return; if (!isRadioAvailableOnAllSubs()) { log("Radio is in Invalid state, Ignore Updating User Preference!!!"); return; } - final int defaultDataSubId = mSubController.getDefaultDataSubId(); + final int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); - if (DBG) log("updateUserPreferences: dds = " + defaultDataSubId + " voice = " - + mSubController.getDefaultVoiceSubId() + - " sms = " + mSubController.getDefaultSmsSubId()); + if (DBG) { + log("updateUserPreferences: dds = " + defaultDataSubId + " voice = " + + mSubscriptionManagerService.getDefaultVoiceSubId() + + " sms = " + mSubscriptionManagerService.getDefaultSmsSubId()); + } int autoDefaultSubId = primarySubList.get(0); if ((primarySubList.size() == 1) && !smsSelected) { - mSubController.setDefaultSmsSubId(autoDefaultSubId); + mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId); } if ((primarySubList.size() == 1) && !voiceSelected) { - mSubController.setDefaultVoiceSubId(autoDefaultSubId); + mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId); } int userPrefDataSubId = getUserPrefDataSubIdFromDB(); - if (DBG) log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId - + " next active subId " + autoDefaultSubId); + log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId + + " next active subId " + autoDefaultSubId); // If earlier user selected DDS is now available, set that as DDS subId. - if (primarySubList.contains(userPrefDataSubId) && - SubscriptionManager.isValidSubscriptionId(userPrefDataSubId) && - (defaultDataSubId != userPrefDataSubId)) { - mSubController.setDefaultDataSubId(userPrefDataSubId); + if (primarySubList.contains(userPrefDataSubId) + && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId) + && (defaultDataSubId != userPrefDataSubId)) { + mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId); } else if (!dataSelected) { - mSubController.setDefaultDataSubId(autoDefaultSubId); + mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId); } - if (DBG) { - log("updateUserPreferences: after dds = " + mSubController.getDefaultDataSubId() - + " voice = " + mSubController.getDefaultVoiceSubId() + " sms = " - + mSubController.getDefaultSmsSubId()); + log("updateUserPreferences: after dds = " + + mSubscriptionManagerService.getDefaultDataSubId() + " voice = " + + mSubscriptionManagerService.getDefaultVoiceSubId() + " sms = " + + mSubscriptionManagerService.getDefaultSmsSubId()); } } diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java index c9ebfd546c7d6f9c3b72ceecba352349cf4332b5..7f9ff798b3ed44fa5bb4c7fd184d2b27932cc866 100644 --- a/src/java/com/android/internal/telephony/NetworkIndication.java +++ b/src/java/com/android/internal/telephony/NetworkIndication.java @@ -16,14 +16,17 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_PRL_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELL_INFO_LIST; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_LCEDATA_RECV; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NETWORK_SCAN_RESULT; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NITZ_TIME_RECEIVED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_REGISTRATION_FAILED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESTRICTED_STATE_CHANGED; @@ -39,6 +42,7 @@ import android.telephony.AnomalyReporter; import android.telephony.BarringInfo; import android.telephony.CellIdentity; import android.telephony.CellInfo; +import android.telephony.EmergencyRegResult; import android.telephony.LinkCapacityEstimate; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhysicalChannelConfig; @@ -72,7 +76,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { public void barringInfoChanged(int indicationType, android.hardware.radio.network.CellIdentity cellIdentity, android.hardware.radio.network.BarringInfo[] barringInfos) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); if (cellIdentity == null || barringInfos == null) { reportAnomaly(UUID.fromString("645b16bb-c930-4c1c-9c5d-568696542e05"), @@ -84,7 +88,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { BarringInfo cbi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity), RILUtils.convertHalBarringInfoList(barringInfos)); - mRil.mBarringInfoChangedRegistrants.notifyRegistrants(new AsyncResult(null, cbi, null)); + mRil.notifyBarringInfoChanged(cbi); } /** @@ -93,7 +97,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { * @param version PRL version after PRL changes */ public void cdmaPrlChanged(int indicationType, int version) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); int[] response = new int[]{version}; @@ -109,7 +113,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { */ public void cellInfoList(int indicationType, android.hardware.radio.network.CellInfo[] records) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); ArrayList response = RILUtils.convertHalCellInfoList(records); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response); mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null)); @@ -122,7 +126,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { */ public void currentLinkCapacityEstimate(int indicationType, android.hardware.radio.network.LinkCapacityEstimate lce) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); List response = RILUtils.convertHalLceData(lce); @@ -140,27 +144,34 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { */ public void currentPhysicalChannelConfigs(int indicationType, android.hardware.radio.network.PhysicalChannelConfig[] configs) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); List response = new ArrayList<>(configs.length); try { for (android.hardware.radio.network.PhysicalChannelConfig config : configs) { PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder(); + int band = PhysicalChannelConfig.BAND_UNKNOWN; switch (config.band.getTag()) { case android.hardware.radio.network.PhysicalChannelConfigBand.geranBand: - builder.setBand(config.band.getGeranBand()); + band = config.band.getGeranBand(); break; case android.hardware.radio.network.PhysicalChannelConfigBand.utranBand: - builder.setBand(config.band.getUtranBand()); + band = config.band.getUtranBand(); break; case android.hardware.radio.network.PhysicalChannelConfigBand.eutranBand: - builder.setBand(config.band.getEutranBand()); + band = config.band.getEutranBand(); break; case android.hardware.radio.network.PhysicalChannelConfigBand.ngranBand: - builder.setBand(config.band.getNgranBand()); + band = config.band.getNgranBand(); break; default: mRil.riljLoge("Unsupported band type " + config.band.getTag()); } + if (band == PhysicalChannelConfig.BAND_UNKNOWN) { + mRil.riljLoge("Unsupported unknown band."); + return; + } else { + builder.setBand(band); + } response.add(builder.setCellConnectionStatus( RILUtils.convertHalCellConnectionStatus(config.status)) .setDownlinkChannelNumber(config.downlinkChannelNumber) @@ -191,7 +202,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { */ public void currentSignalStrength(int indicationType, android.hardware.radio.network.SignalStrength signalStrength) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); SignalStrength ssInitial = RILUtils.convertHalSignalStrength(signalStrength); @@ -209,7 +220,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { * @param indicationType Type of radio indication */ public void imsNetworkStateChanged(int indicationType) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED); @@ -223,7 +234,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { */ public void networkScanResult(int indicationType, android.hardware.radio.network.NetworkScanResult result) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); ArrayList cellInfos = RILUtils.convertHalCellInfoList(result.networkInfos); NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos); @@ -236,7 +247,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { * @param indicationType Type of radio indication */ public void networkStateChanged(int indicationType) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED); @@ -256,7 +267,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { */ public void nitzTimeReceived(int indicationType, String nitzTime, @ElapsedRealtimeLong long receivedTimeMs, long ageMs) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime); @@ -303,23 +314,32 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { public void registrationFailed(int indicationType, android.hardware.radio.network.CellIdentity cellIdentity, String chosenPlmn, @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); CellIdentity ci = RILUtils.convertHalCellIdentity(cellIdentity); if (ci == null || TextUtils.isEmpty(chosenPlmn) || (domain & NetworkRegistrationInfo.DOMAIN_CS_PS) == 0 || (domain & ~NetworkRegistrationInfo.DOMAIN_CS_PS) != 0 || causeCode < 0 || additionalCauseCode < 0 || (causeCode == Integer.MAX_VALUE && additionalCauseCode == Integer.MAX_VALUE)) { - reportAnomaly(UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb4"), + reportAnomaly(UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb5"), "Invalid registrationFailed indication"); - mRil.riljLoge("Invalid registrationFailed indication"); + mRil.riljLoge("Invalid registrationFailed indication (ci is null)=" + (ci == null) + + " (chosenPlmn is empty)=" + TextUtils.isEmpty(chosenPlmn) + + " (is CS/PS)=" + ((domain & NetworkRegistrationInfo.DOMAIN_CS_PS) == 0) + + " (only CS/PS)=" + ((domain & ~NetworkRegistrationInfo.DOMAIN_CS_PS) != 0) + + " (causeCode)=" + causeCode + + " (additionalCauseCode)=" + additionalCauseCode); return; } + RegistrationFailedEvent registrationFailedEvent = new RegistrationFailedEvent( + ci, chosenPlmn, domain, causeCode, additionalCauseCode); + if (mRil.isLogOrTrace()) { + mRil.unsljLogMore(RIL_UNSOL_REGISTRATION_FAILED, registrationFailedEvent.toString()); + } mRil.mRegistrationFailedRegistrant.notifyRegistrant( - new AsyncResult(null, new RegistrationFailedEvent( - ci, chosenPlmn, domain, causeCode, additionalCauseCode), null)); + new AsyncResult(null, registrationFailedEvent, null)); } /** @@ -328,7 +348,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { * @param state Bitmask of restricted state as defined by PhoneRestrictedState */ public void restrictedStateChanged(int indicationType, int state) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state); @@ -344,7 +364,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { */ public void suppSvcNotify(int indicationType, android.hardware.radio.network.SuppSvcNotification suppSvcNotification) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); SuppServiceNotification notification = new SuppServiceNotification(); notification.notificationType = suppSvcNotification.isMT ? 1 : 0; @@ -368,7 +388,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { * @param rat Current new voice rat */ public void voiceRadioTechChanged(int indicationType, int rat) { - mRil.processIndication(RIL.NETWORK_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); int[] response = new int[] {rat}; @@ -380,6 +400,25 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub { new AsyncResult(null, response, null)); } + /** + * Emergency Scan Results. + * @param indicationType Type of radio indication + * @param result the result of the Emergency Network Scan + */ + public void emergencyNetworkScanResult(int indicationType, + android.hardware.radio.network.EmergencyRegResult result) { + mRil.processIndication(HAL_SERVICE_NETWORK, indicationType); + + EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(result); + + if (mRil.isLogOrTrace()) { + mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT, response); + } + + mRil.mEmergencyNetworkScanRegistrants.notifyRegistrants( + new AsyncResult(null, response, null)); + } + @Override public String getInterfaceHash() { return IRadioNetworkIndication.HASH; diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java index d9f70fdca5280ebe54b7dd0418f580deb84a69bc..b1eb9265ab23160cf6843458c1c77a4a08e74fc4 100644 --- a/src/java/com/android/internal/telephony/NetworkResponse.java +++ b/src/java/com/android/internal/telephony/NetworkResponse.java @@ -16,12 +16,15 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; + import android.hardware.radio.RadioError; import android.hardware.radio.RadioResponseInfo; import android.hardware.radio.network.IRadioNetworkResponse; import android.os.AsyncResult; import android.telephony.BarringInfo; import android.telephony.CellInfo; +import android.telephony.EmergencyRegResult; import android.telephony.LinkCapacityEstimate; import android.telephony.RadioAccessSpecifier; import android.telephony.SignalStrength; @@ -57,7 +60,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { int halRadioAccessFamilyBitmap) { int networkTypeBitmask = RILUtils.convertHalNetworkTypeBitMask(halRadioAccessFamilyBitmap); mRil.mAllowedNetworkTypesBitmask = networkTypeBitmask; - RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, networkTypeBitmask); + RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, networkTypeBitmask); } /** @@ -65,7 +68,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { * @param bandModes List of RadioBandMode listing supported modes */ public void getAvailableBandModesResponse(RadioResponseInfo responseInfo, int[] bandModes) { - RadioResponse.responseIntArrayList(RIL.NETWORK_SERVICE, mRil, responseInfo, + RadioResponse.responseIntArrayList(HAL_SERVICE_NETWORK, mRil, responseInfo, RILUtils.primitiveArrayToArrayList(bandModes)); } @@ -75,7 +78,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getAvailableNetworksResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.OperatorInfo[] networkInfos) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { ArrayList ret = new ArrayList<>(); @@ -98,7 +101,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { public void getBarringInfoResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.CellIdentity cellIdentity, android.hardware.radio.network.BarringInfo[] barringInfos) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { BarringInfo bi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity), @@ -118,7 +121,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { * @param type CdmaRoamingType defined in types.hal */ public void getCdmaRoamingPreferenceResponse(RadioResponseInfo responseInfo, int type) { - RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, type); + RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, type); } /** @@ -127,7 +130,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getCellInfoListResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.CellInfo[] cellInfo) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { ArrayList ret = RILUtils.convertHalCellInfoList(cellInfo); @@ -144,7 +147,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getDataRegistrationStateResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.RegStateResult dataRegResponse) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { @@ -161,7 +164,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getImsRegistrationStateResponse(RadioResponseInfo responseInfo, boolean isRegistered, int ratFamily) { - RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, isRegistered ? 1 : 0, + RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, isRegistered ? 1 : 0, ratFamily == android.hardware.radio.RadioTechnologyFamily.THREE_GPP ? PhoneConstants.PHONE_TYPE_GSM : PhoneConstants.PHONE_TYPE_CDMA); } @@ -171,7 +174,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { * @param selection false for automatic selection, true for manual selection */ public void getNetworkSelectionModeResponse(RadioResponseInfo responseInfo, boolean selection) { - RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, selection ? 1 : 0); + RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, selection ? 1 : 0); } /** @@ -183,7 +186,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { public void getOperatorResponse(RadioResponseInfo responseInfo, String longName, String shortName, String numeric) { RadioResponse.responseStrings( - RIL.NETWORK_SERVICE, mRil, responseInfo, longName, shortName, numeric); + HAL_SERVICE_NETWORK, mRil, responseInfo, longName, shortName, numeric); } /** @@ -192,7 +195,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getSignalStrengthResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.SignalStrength signalStrength) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength); @@ -209,7 +212,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getSystemSelectionChannelsResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.RadioAccessSpecifier[] halSpecifiers) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { ArrayList specifiers = new ArrayList<>(); @@ -229,7 +232,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { * @param rat Current voice RAT */ public void getVoiceRadioTechnologyResponse(RadioResponseInfo responseInfo, int rat) { - RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, rat); + RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, rat); } /** @@ -238,7 +241,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getVoiceRegistrationStateResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.RegStateResult voiceRegResponse) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { RadioResponse.sendMessageResponse(rr.mResult, voiceRegResponse); @@ -254,7 +257,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void isNrDualConnectivityEnabledResponse(RadioResponseInfo responseInfo, boolean isEnabled) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { @@ -270,7 +273,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void pullLceDataResponse(RadioResponseInfo responseInfo, android.hardware.radio.network.LceDataInfo lceInfo) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { List ret = RILUtils.convertHalLceData(lceInfo); @@ -285,105 +288,105 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void setAllowedNetworkTypesBitmapResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setBandModeResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setBarringPasswordResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCdmaRoamingPreferenceResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setIndicationFilterResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setLinkCapacityReportingCriteriaResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setLocationUpdatesResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setNetworkSelectionModeAutomaticResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setNetworkSelectionModeManualResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setNrDualConnectivityStateResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setSignalStrengthReportingCriteriaResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setSuppServiceNotificationsResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial number and error. */ public void setSystemSelectionChannelsResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void startNetworkScanResponse(RadioResponseInfo responseInfo) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { NetworkScanResult nsr = null; if (responseInfo.error == RadioError.NONE) { @@ -399,7 +402,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void stopNetworkScanResponse(RadioResponseInfo responseInfo) { - RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); if (rr != null) { NetworkScanResult nsr = null; if (responseInfo.error == RadioError.NONE) { @@ -417,14 +420,14 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void supplyNetworkDepersonalizationResponse(RadioResponseInfo responseInfo, int retriesRemaining) { - RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, retriesRemaining); + RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, retriesRemaining); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setUsageSettingResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } /** @@ -433,7 +436,91 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { */ public void getUsageSettingResponse(RadioResponseInfo responseInfo, /* @TelephonyManager.UsageSetting */ int usageSetting) { - RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, usageSetting); + RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, usageSetting); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error + * @param regState the current registration state of the modem. + */ + public void setEmergencyModeResponse(RadioResponseInfo responseInfo, + android.hardware.radio.network.EmergencyRegResult regState) { + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); + + if (rr != null) { + EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(regState); + if (responseInfo.error == RadioError.NONE) { + RadioResponse.sendMessageResponse(rr.mResult, response); + } + mRil.processResponseDone(rr, responseInfo, response); + } + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error + */ + public void triggerEmergencyNetworkScanResponse(RadioResponseInfo responseInfo) { + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error + */ + public void exitEmergencyModeResponse(RadioResponseInfo responseInfo) { + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error + */ + public void cancelEmergencyNetworkScanResponse(RadioResponseInfo responseInfo) { + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error + */ + public void setNullCipherAndIntegrityEnabledResponse(RadioResponseInfo responseInfo) { + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error. + * @param isEnabled Indicates whether null cipher and integrity is enabled, indicating + * potentially unencrypted communication + */ + public void isNullCipherAndIntegrityEnabledResponse(RadioResponseInfo responseInfo, + boolean isEnabled) { + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); + + if (rr != null) { + if (responseInfo.error == RadioError.NONE) { + RadioResponse.sendMessageResponse(rr.mResult, isEnabled); + } + mRil.processResponseDone(rr, responseInfo, isEnabled); + } + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error. + * @param isEnabled Indicates whether N1 mode is enabled or not. + */ + public void isN1ModeEnabledResponse(RadioResponseInfo responseInfo, boolean isEnabled) { + RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo); + + if (rr != null) { + if (responseInfo.error == RadioError.NONE) { + RadioResponse.sendMessageResponse(rr.mResult, isEnabled); + } + mRil.processResponseDone(rr, responseInfo, isEnabled); + } + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error. + */ + public void setN1ModeEnabledResponse(RadioResponseInfo responseInfo) { + RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo); } @Override @@ -445,4 +532,5 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub { public int getInterfaceVersion() { return IRadioNetworkResponse.VERSION; } + } diff --git a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java index b15dc594b30955da757ad04ef8e79516852e2af4..75675669c17560d39293ab0804034656a94ed187 100644 --- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java +++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java @@ -197,14 +197,7 @@ public final class NetworkScanRequestTracker { public static Set getAllowedMccMncsForLocationRestrictedScan(Context context) { final long token = Binder.clearCallingIdentity(); try { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - return SubscriptionManagerService.getInstance() - .getAvailableSubscriptionInfoList(context.getOpPackageName(), - context.getAttributionTag()).stream() - .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo) - .collect(Collectors.toSet()); - } - return SubscriptionController.getInstance() + return SubscriptionManagerService.getInstance() .getAvailableSubscriptionInfoList(context.getOpPackageName(), context.getAttributionTag()).stream() .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo) @@ -481,21 +474,21 @@ public final class NetworkScanRequestTracker { notifyMessenger(nsri, notifyMsg, rilErrorToScanError(nsr.scanError), nsr.networkInfos); if (nsr.scanStatus == NetworkScanResult.SCAN_STATUS_COMPLETE) { - deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true); nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler); + deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true); } } else { if (nsr.networkInfos != null) { notifyMessenger(nsri, notifyMsg, rilErrorToScanError(nsr.scanError), nsr.networkInfos); } - deleteScanAndMayNotify(nsri, rilErrorToScanError(nsr.scanError), true); nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler); + deleteScanAndMayNotify(nsri, rilErrorToScanError(nsr.scanError), true); } } else { logEmptyResultOrException(ar); - deleteScanAndMayNotify(nsri, NetworkScan.ERROR_RADIO_INTERFACE_ERROR, true); nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler); + deleteScanAndMayNotify(nsri, NetworkScan.ERROR_RADIO_INTERFACE_ERROR, true); } } @@ -523,6 +516,7 @@ public final class NetworkScanRequestTracker { Log.e(TAG, "EVENT_STOP_NETWORK_SCAN_DONE: nsri is null"); return; } + nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler); if (ar.exception == null && ar.result != null) { deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true); } else { @@ -535,7 +529,6 @@ public final class NetworkScanRequestTracker { Log.wtf(TAG, "EVENT_STOP_NETWORK_SCAN_DONE: ar.exception can not be null!"); } } - nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler); } // Interrupts the live scan is the scanId matches the mScanId of the mLiveRequestInfo. diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java index 4093015207dd5725780122309001d63221bba925..beebf2206edb3031d10c0e4ae0e0b4ef01faf97b 100644 --- a/src/java/com/android/internal/telephony/NetworkTypeController.java +++ b/src/java/com/android/internal/telephony/NetworkTypeController.java @@ -16,6 +16,7 @@ package com.android.internal.telephony; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; @@ -31,7 +32,6 @@ import android.telephony.CarrierConfigManager; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhysicalChannelConfig; import android.telephony.ServiceState; -import android.telephony.SubscriptionManager; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.telephony.data.DataCallResponse; @@ -53,6 +53,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -84,30 +85,35 @@ public class NetworkTypeController extends StateMachine { /** Stop all timers and go to current state. */ public static final int EVENT_UPDATE = 0; /** Quit after processing all existing messages. */ - public static final int EVENT_QUIT = 1; - private static final int EVENT_DATA_RAT_CHANGED = 2; - private static final int EVENT_NR_STATE_CHANGED = 3; - private static final int EVENT_NR_FREQUENCY_CHANGED = 4; - private static final int EVENT_PHYSICAL_LINK_STATUS_CHANGED = 5; - private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6; - private static final int EVENT_CARRIER_CONFIG_CHANGED = 7; - private static final int EVENT_PRIMARY_TIMER_EXPIRED = 8; - private static final int EVENT_SECONDARY_TIMER_EXPIRED = 9; - private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 10; - private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 11; - private static final int EVENT_INITIALIZE = 12; - private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 13; - private static final int EVENT_BANDWIDTH_CHANGED = 15; - private static final int EVENT_UPDATE_NR_ADVANCED_STATE = 16; - private static final int EVENT_DEVICE_IDLE_MODE_CHANGED = 17; + private static final int EVENT_QUIT = 1; + /** Initialize all events. */ + private static final int EVENT_INITIALIZE = 2; + /** Event for service state changed (data rat, bandwidth, NR state, NR frequency, etc). */ + private static final int EVENT_SERVICE_STATE_CHANGED = 3; + /** Event for physical link status changed. */ + private static final int EVENT_PHYSICAL_LINK_STATUS_CHANGED = 4; + /** Event for physical channel config indications turned on/off. */ + private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 5; + /** Event for carrier configs changed. */ + private static final int EVENT_CARRIER_CONFIG_CHANGED = 6; + /** Event for primary timer expired. If a secondary timer exists, it will begin afterwards. */ + private static final int EVENT_PRIMARY_TIMER_EXPIRED = 7; + /** Event for secondary timer expired. */ + private static final int EVENT_SECONDARY_TIMER_EXPIRED = 8; + /** Event for radio off or unavailable. */ + private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 9; + /** 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; + /** 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; private static final String[] sEvents = new String[EVENT_DEVICE_IDLE_MODE_CHANGED + 1]; static { sEvents[EVENT_UPDATE] = "EVENT_UPDATE"; sEvents[EVENT_QUIT] = "EVENT_QUIT"; - sEvents[EVENT_DATA_RAT_CHANGED] = "EVENT_DATA_RAT_CHANGED"; - sEvents[EVENT_NR_STATE_CHANGED] = "EVENT_NR_STATE_CHANGED"; - sEvents[EVENT_NR_FREQUENCY_CHANGED] = "EVENT_NR_FREQUENCY_CHANGED"; + sEvents[EVENT_SERVICE_STATE_CHANGED] = "EVENT_SERVICE_STATE_CHANGED"; sEvents[EVENT_PHYSICAL_LINK_STATUS_CHANGED] = "EVENT_PHYSICAL_LINK_STATUS_CHANGED"; sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED"; @@ -118,25 +124,15 @@ public class NetworkTypeController extends StateMachine { 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_BANDWIDTH_CHANGED] = "EVENT_BANDWIDTH_CHANGED"; - sEvents[EVENT_UPDATE_NR_ADVANCED_STATE] = "EVENT_UPDATE_NR_ADVANCED_STATE"; sEvents[EVENT_DEVICE_IDLE_MODE_CHANGED] = "EVENT_DEVICE_IDLE_MODE_CHANGED"; } - private final Phone mPhone; - private final DisplayInfoController mDisplayInfoController; - private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + private final @NonNull Phone mPhone; + private final @NonNull DisplayInfoController mDisplayInfoController; + private final @NonNull BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { - case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED: - if (intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, - SubscriptionManager.INVALID_PHONE_INDEX) == mPhone.getPhoneId() - && !intent.getBooleanExtra( - CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK, false)) { - sendMessage(EVENT_CARRIER_CONFIG_CHANGED); - } - break; case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: sendMessage(EVENT_DEVICE_IDLE_MODE_CHANGED); break; @@ -144,9 +140,22 @@ public class NetworkTypeController extends StateMachine { } }; - private Map mOverrideTimerRules = new HashMap<>(); - private String mLteEnhancedPattern = ""; - private int mOverrideNetworkType; + private final @NonNull CarrierConfigManager.CarrierConfigChangeListener + mCarrierConfigChangeListener = + new CarrierConfigManager.CarrierConfigChangeListener() { + @Override + public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId, + int specificCarrierId) { + // CarrierConfigChangeListener wouldn't send notification on device unlock + if (slotIndex == mPhone.getPhoneId()) { + sendMessage(EVENT_CARRIER_CONFIG_CHANGED); + } + } + }; + + private @NonNull Map mOverrideTimerRules = new HashMap<>(); + private @NonNull String mLteEnhancedPattern = ""; + private @Annotation.OverrideNetworkType int mOverrideNetworkType; private boolean mIsPhysicalChannelConfigOn; private boolean mIsPrimaryTimerActive; private boolean mIsSecondaryTimerActive; @@ -154,10 +163,10 @@ public class NetworkTypeController extends StateMachine { private int mLtePlusThresholdBandwidth; private int mNrAdvancedThresholdBandwidth; private boolean mIncludeLteForNrAdvancedThresholdBandwidth; - private int[] mAdditionalNrAdvancedBandsList; - private String mPrimaryTimerState; - private String mSecondaryTimerState; - private String mPreviousState; + private @NonNull int[] mAdditionalNrAdvancedBandsList; + private @NonNull String mPrimaryTimerState; + private @NonNull String mSecondaryTimerState; + private @NonNull String mPreviousState; private @LinkStatus int mPhysicalLinkStatus; private boolean mIsPhysicalChannelConfig16Supported; private boolean mIsNrAdvancedAllowedByPco = false; @@ -169,6 +178,10 @@ public class NetworkTypeController extends StateMachine { private @Nullable DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null; private @Nullable DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null; + // Cached copies below to prevent race conditions + private @NonNull ServiceState mServiceState; + private @Nullable List mPhysicalChannelConfigs; + /** * NetworkTypeController constructor. * @@ -181,14 +194,22 @@ public class NetworkTypeController extends StateMachine { mDisplayInfoController = displayInfoController; mOverrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE; mIsPhysicalChannelConfigOn = true; + mPrimaryTimerState = ""; + mSecondaryTimerState = ""; + mPreviousState = ""; DefaultState defaultState = new DefaultState(); addState(defaultState); addState(mLegacyState, defaultState); addState(mIdleState, defaultState); addState(mLteConnectedState, defaultState); addState(mNrConnectedState, defaultState); + addState(mNrConnectedAdvancedState, defaultState); setInitialState(defaultState); start(); + + mServiceState = mPhone.getServiceStateTracker().getServiceState(); + mPhysicalChannelConfigs = mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + sendMessage(EVENT_INITIALIZE); } @@ -201,10 +222,21 @@ public class NetworkTypeController extends StateMachine { } /** - * @return True if either the primary or secondary 5G hysteresis timer is active, - * and false if neither are. + * @return The current data network type, used to create TelephonyDisplayInfo in + * DisplayInfoController. */ - public boolean is5GHysteresisActive() { + public @Annotation.NetworkType int getDataNetworkType() { + NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + return nri == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN + : nri.getAccessNetworkTechnology(); + } + + /** + * @return {@code true} if either the primary or secondary 5G icon timers are active, + * and {@code false} if neither are. + */ + public boolean areAnyTimersActive() { return mIsPrimaryTimerActive || mIsSecondaryTimerActive; } @@ -215,35 +247,30 @@ public class NetworkTypeController extends StateMachine { EVENT_PREFERRED_NETWORK_MODE_CHANGED, null); mPhone.registerForPhysicalChannelConfig(getHandler(), EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED, null); - mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged( - AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(), - EVENT_DATA_RAT_CHANGED, null); - mPhone.getServiceStateTracker().registerForBandwidthChanged( - getHandler(), EVENT_BANDWIDTH_CHANGED, null); + mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(), + EVENT_SERVICE_STATE_CHANGED, null); mIsPhysicalChannelConfig16Supported = mPhone.getContext().getSystemService( TelephonyManager.class).isRadioInterfaceCapabilitySupported( TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED); - mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(), - EVENT_NR_STATE_CHANGED, null); - mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(), - EVENT_NR_FREQUENCY_CHANGED, null); mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(), EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null); IntentFilter filter = new IntentFilter(); - filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone); + CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class); + ccm.registerCarrierConfigChangeListener(Runnable::run, mCarrierConfigChangeListener); } private void unRegisterForAllEvents() { mPhone.unregisterForRadioOffOrNotAvailable(getHandler()); mPhone.unregisterForPreferredNetworkTypeChanged(getHandler()); - mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged( - AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler()); - mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler()); - mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler()); + mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler()); mPhone.getDeviceStateMonitor().unregisterForPhysicalChannelConfigNotifChanged(getHandler()); mPhone.getContext().unregisterReceiver(mIntentReceiver); + CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class); + if (mCarrierConfigChangeListener != null) { + ccm.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener); + } } private void parseCarrierConfigs() { @@ -276,11 +303,10 @@ public class NetworkTypeController extends StateMachine { mNrAdvancedCapableByPcoChangedCallback = new DataNetworkControllerCallback(getHandler()::post) { @Override - public void onNrAdvancedCapableByPcoChanged( - boolean nrAdvancedCapable) { + public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) { log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable); mIsNrAdvancedAllowedByPco = nrAdvancedCapable; - sendMessage(EVENT_UPDATE_NR_ADVANCED_STATE); + sendMessage(EVENT_UPDATE); } }; mPhone.getDataNetworkController().registerDataNetworkControllerCallback( @@ -324,7 +350,7 @@ public class NetworkTypeController extends StateMachine { if (!TextUtils.isEmpty(icons)) { // Format: "STATE:ICON,STATE2:ICON2" for (String pair : icons.trim().split(",")) { - String[] kv = (pair.trim().toLowerCase()).split(":"); + String[] kv = (pair.trim().toLowerCase(Locale.ROOT)).split(":"); if (kv.length != 2) { if (DBG) loge("Invalid 5G icon configuration, config = " + pair); continue; @@ -351,7 +377,7 @@ public class NetworkTypeController extends StateMachine { if (!TextUtils.isEmpty(timers)) { // Format: "FROM_STATE,TO_STATE,DURATION;FROM_STATE_2,TO_STATE_2,DURATION_2" for (String triple : timers.trim().split(";")) { - String[] kv = (triple.trim().toLowerCase()).split(","); + String[] kv = (triple.trim().toLowerCase(Locale.ROOT)).split(","); if (kv.length != 3) { if (DBG) loge("Invalid 5G icon timer configuration, config = " + triple); continue; @@ -377,7 +403,7 @@ public class NetworkTypeController extends StateMachine { if (!TextUtils.isEmpty(secondaryTimers)) { // Format: "PRIMARY_STATE,TO_STATE,DURATION;PRIMARY_STATE_2,TO_STATE_2,DURATION_2" for (String triple : secondaryTimers.trim().split(";")) { - String[] kv = (triple.trim().toLowerCase()).split(","); + String[] kv = (triple.trim().toLowerCase(Locale.ROOT)).split(","); if (kv.length != 3) { if (DBG) { loge("Invalid 5G icon secondary timer configuration, config = " + triple); @@ -419,7 +445,7 @@ public class NetworkTypeController extends StateMachine { int displayNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE; int dataNetworkType = getDataNetworkType(); boolean nrNsa = isLte(dataNetworkType) - && mPhone.getServiceState().getNrState() != NetworkRegistrationInfo.NR_STATE_NONE; + && mServiceState.getNrState() != NetworkRegistrationInfo.NR_STATE_NONE; boolean nrSa = dataNetworkType == TelephonyManager.NETWORK_TYPE_NR; // NR display is not accurate when physical channel config notifications are off @@ -450,7 +476,7 @@ public class NetworkTypeController extends StateMachine { keys.add(STATE_CONNECTED_NR_ADVANCED); } } else { - switch (mPhone.getServiceState().getNrState()) { + switch (mServiceState.getNrState()) { case NetworkRegistrationInfo.NR_STATE_CONNECTED: if (isNrAdvanced()) { keys.add(STATE_CONNECTED_NR_ADVANCED); @@ -480,9 +506,9 @@ public class NetworkTypeController extends StateMachine { private @Annotation.OverrideNetworkType int getLteDisplayType() { int value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE; if ((getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA - || mPhone.getServiceState().isUsingCarrierAggregation()) - && (IntStream.of(mPhone.getServiceState().getCellBandwidths()).sum() - > mLtePlusThresholdBandwidth)) { + || mServiceState.isUsingCarrierAggregation()) + && IntStream.of(mServiceState.getCellBandwidths()).sum() + > mLtePlusThresholdBandwidth) { value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA; } if (isLteEnhancedAvailable()) { @@ -496,8 +522,8 @@ public class NetworkTypeController extends StateMachine { return false; } Pattern stringPattern = Pattern.compile(mLteEnhancedPattern); - for (String opName : new String[] {mPhone.getServiceState().getOperatorAlphaLongRaw(), - mPhone.getServiceState().getOperatorAlphaShortRaw()}) { + for (String opName : new String[] {mServiceState.getOperatorAlphaLongRaw(), + mServiceState.getOperatorAlphaShortRaw()}) { if (!TextUtils.isEmpty(opName)) { Matcher matcher = stringPattern.matcher(opName); if (matcher.find()) { @@ -517,8 +543,6 @@ public class NetworkTypeController extends StateMachine { if (DBG) log("DefaultState: process " + getEventName(msg.what)); switch (msg.what) { case EVENT_UPDATE: - case EVENT_PREFERRED_NETWORK_MODE_CHANGED: - if (DBG) log("Reset timers since preferred network mode changed."); resetAllTimers(); transitionToCurrentState(); break; @@ -535,20 +559,10 @@ public class NetworkTypeController extends StateMachine { registerForAllEvents(); parseCarrierConfigs(); break; - case EVENT_DATA_RAT_CHANGED: - case EVENT_NR_STATE_CHANGED: - case EVENT_NR_FREQUENCY_CHANGED: - case EVENT_UPDATE_NR_ADVANCED_STATE: - // ignored - break; - case EVENT_BANDWIDTH_CHANGED: - // Update in case of LTE/LTE+ switch - updateOverrideNetworkType(); - break; - case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: - if (isUsingPhysicalChannelConfigForRrcDetection()) { - mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); - } + case EVENT_SERVICE_STATE_CHANGED: + mServiceState = mPhone.getServiceStateTracker().getServiceState(); + if (DBG) log("ServiceState updated: " + mServiceState); + transitionToCurrentState(); break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: AsyncResult ar = (AsyncResult) msg.obj; @@ -590,6 +604,20 @@ public class NetworkTypeController extends StateMachine { resetAllTimers(); transitionTo(mLegacyState); break; + case EVENT_PREFERRED_NETWORK_MODE_CHANGED: + if (DBG) log("Reset timers since preferred network mode changed."); + resetAllTimers(); + transitionToCurrentState(); + break; + case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: + mPhysicalChannelConfigs = + mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + if (isUsingPhysicalChannelConfigForRrcDetection()) { + mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); + } + transitionToCurrentState(); + break; case EVENT_DEVICE_IDLE_MODE_CHANGED: PowerManager pm = mPhone.getContext().getSystemService(PowerManager.class); mIsDeviceIdleMode = pm.isDeviceIdleMode(); @@ -636,11 +664,19 @@ public class NetworkTypeController extends StateMachine { public boolean processMessage(Message msg) { if (DBG) log("LegacyState: process " + getEventName(msg.what)); updateTimers(); - int rat = getDataNetworkType(); switch (msg.what) { - case EVENT_DATA_RAT_CHANGED: + case EVENT_SERVICE_STATE_CHANGED: + mServiceState = mPhone.getServiceStateTracker().getServiceState(); + if (DBG) log("ServiceState updated: " + mServiceState); + // fallthrough + case EVENT_UPDATE: + int rat = getDataNetworkType(); if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) { - transitionTo(mNrConnectedState); + if (isNrAdvanced()) { + transitionTo(mNrConnectedAdvancedState); + } else { + transitionTo(mNrConnectedState); + } } else if (isLte(rat) && isNrNotRestricted()) { transitionWithTimerTo(isPhysicalLinkActive() ? mLteConnectedState : mIdleState); @@ -653,21 +689,10 @@ public class NetworkTypeController extends StateMachine { } mIsNrRestricted = isNrRestricted(); break; - case EVENT_NR_STATE_CHANGED: - if (isNrConnected()) { - transitionTo(mNrConnectedState); - } else if (isLte(rat) && isNrNotRestricted()) { - transitionWithTimerTo(isPhysicalLinkActive() - ? mLteConnectedState : mIdleState); - } else if (isLte(rat) && isNrRestricted()) { - updateOverrideNetworkType(); - } - mIsNrRestricted = isNrRestricted(); - break; - case EVENT_NR_FREQUENCY_CHANGED: - // ignored - break; case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: + mPhysicalChannelConfigs = + mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) { @@ -675,8 +700,6 @@ public class NetworkTypeController extends StateMachine { resetAllTimers(); } } - // Update in case of LTE/LTE+ switch - updateOverrideNetworkType(); break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: AsyncResult ar = (AsyncResult) msg.obj; @@ -723,52 +746,52 @@ public class NetworkTypeController extends StateMachine { if (DBG) log("IdleState: process " + getEventName(msg.what)); updateTimers(); switch (msg.what) { - case EVENT_DATA_RAT_CHANGED: + case EVENT_SERVICE_STATE_CHANGED: + mServiceState = mPhone.getServiceStateTracker().getServiceState(); + if (DBG) log("ServiceState updated: " + mServiceState); + // fallthrough + case EVENT_UPDATE: int rat = getDataNetworkType(); - if (rat == TelephonyManager.NETWORK_TYPE_NR) { - transitionTo(mNrConnectedState); + if (rat == TelephonyManager.NETWORK_TYPE_NR + || (isLte(rat) && isNrConnected())) { + if (isNrAdvanced()) { + transitionTo(mNrConnectedAdvancedState); + } else { + transitionTo(mNrConnectedState); + } } else if (!isLte(rat) || !isNrNotRestricted()) { transitionWithTimerTo(mLegacyState); + } else { + if (isPhysicalLinkActive()) { + transitionWithTimerTo(mLteConnectedState); + } else { + // Update in case the override network type changed + updateOverrideNetworkType(); + } } break; - case EVENT_NR_STATE_CHANGED: - if (isNrConnected()) { - transitionTo(mNrConnectedState); - } else if (!isNrNotRestricted()) { - transitionWithTimerTo(mLegacyState); - } - break; - case EVENT_NR_FREQUENCY_CHANGED: - // ignore - break; case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: + mPhysicalChannelConfigs = + mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); - if (isNrNotRestricted()) { - // NOT_RESTRICTED_RRC_IDLE -> NOT_RESTRICTED_RRC_CON - if (isPhysicalLinkActive()) { - transitionWithTimerTo(mLteConnectedState); - break; - } + if (isPhysicalLinkActive()) { + transitionWithTimerTo(mLteConnectedState); } else { - log("NR state changed. Sending EVENT_NR_STATE_CHANGED"); - sendMessage(EVENT_NR_STATE_CHANGED); + log("Reevaluating state due to link status changed."); + sendMessage(EVENT_UPDATE); } } - // Update in case of LTE/LTE+ switch - updateOverrideNetworkType(); break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: AsyncResult ar = (AsyncResult) msg.obj; mPhysicalLinkStatus = (int) ar.result; - if (isNrNotRestricted()) { - // NOT_RESTRICTED_RRC_IDLE -> NOT_RESTRICTED_RRC_CON - if (isPhysicalLinkActive()) { - transitionWithTimerTo(mLteConnectedState); - } + if (isPhysicalLinkActive()) { + transitionWithTimerTo(mLteConnectedState); } else { - log("NR state changed. Sending EVENT_NR_STATE_CHANGED"); - sendMessage(EVENT_NR_STATE_CHANGED); + log("Reevaluating state due to link status changed."); + sendMessage(EVENT_UPDATE); } break; default: @@ -807,52 +830,52 @@ public class NetworkTypeController extends StateMachine { if (DBG) log("LteConnectedState: process " + getEventName(msg.what)); updateTimers(); switch (msg.what) { - case EVENT_DATA_RAT_CHANGED: + case EVENT_SERVICE_STATE_CHANGED: + mServiceState = mPhone.getServiceStateTracker().getServiceState(); + if (DBG) log("ServiceState updated: " + mServiceState); + // fallthrough + case EVENT_UPDATE: int rat = getDataNetworkType(); - if (rat == TelephonyManager.NETWORK_TYPE_NR) { - transitionTo(mNrConnectedState); + if (rat == TelephonyManager.NETWORK_TYPE_NR + || (isLte(rat) && isNrConnected())) { + if (isNrAdvanced()) { + transitionTo(mNrConnectedAdvancedState); + } else { + transitionTo(mNrConnectedState); + } } else if (!isLte(rat) || !isNrNotRestricted()) { transitionWithTimerTo(mLegacyState); + } else { + if (!isPhysicalLinkActive()) { + transitionWithTimerTo(mIdleState); + } else { + // Update in case the override network type changed + updateOverrideNetworkType(); + } } break; - case EVENT_NR_STATE_CHANGED: - if (isNrConnected()) { - transitionTo(mNrConnectedState); - } else if (!isNrNotRestricted()) { - transitionWithTimerTo(mLegacyState); - } - break; - case EVENT_NR_FREQUENCY_CHANGED: - // ignore - break; case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: + mPhysicalChannelConfigs = + mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); - if (isNrNotRestricted()) { - // NOT_RESTRICTED_RRC_CON -> NOT_RESTRICTED_RRC_IDLE - if (!isPhysicalLinkActive()) { - transitionWithTimerTo(mIdleState); - break; - } + if (!isPhysicalLinkActive()) { + transitionWithTimerTo(mIdleState); } else { - log("NR state changed. Sending EVENT_NR_STATE_CHANGED"); - sendMessage(EVENT_NR_STATE_CHANGED); + log("Reevaluating state due to link status changed."); + sendMessage(EVENT_UPDATE); } } - // Update in case of LTE/LTE+ switch - updateOverrideNetworkType(); break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: AsyncResult ar = (AsyncResult) msg.obj; mPhysicalLinkStatus = (int) ar.result; - if (isNrNotRestricted()) { - // NOT_RESTRICTED_RRC_CON -> NOT_RESTRICTED_RRC_IDLE - if (!isPhysicalLinkActive()) { - transitionWithTimerTo(mIdleState); - } + if (!isPhysicalLinkActive()) { + transitionWithTimerTo(mIdleState); } else { - log("NR state changed. Sending EVENT_NR_STATE_CHANGED"); - sendMessage(EVENT_NR_STATE_CHANGED); + log("Reevaluating state due to link status changed."); + sendMessage(EVENT_UPDATE); } break; default: @@ -876,28 +899,35 @@ public class NetworkTypeController extends StateMachine { * Device is connected to 5G NR as the primary or secondary cell. */ private final class NrConnectedState extends State { - private boolean mIsNrAdvanced = false; - @Override public void enter() { - if (DBG) log("Entering NrConnectedState(" + getName() + ")"); + if (DBG) log("Entering NrConnectedState"); updateTimers(); updateOverrideNetworkType(); if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) { - mIsNrAdvanced = isNrAdvanced(); mPreviousState = getName(); } } @Override public boolean processMessage(Message msg) { - if (DBG) log("NrConnectedState(" + getName() + "): process " + getEventName(msg.what)); + if (DBG) log("NrConnectedState: process " + getEventName(msg.what)); updateTimers(); - int rat = getDataNetworkType(); switch (msg.what) { - case EVENT_DATA_RAT_CHANGED: - if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) { - updateOverrideNetworkType(); + case EVENT_SERVICE_STATE_CHANGED: + mServiceState = mPhone.getServiceStateTracker().getServiceState(); + if (DBG) log("ServiceState updated: " + mServiceState); + // fallthrough + case EVENT_UPDATE: + int rat = getDataNetworkType(); + if (rat == TelephonyManager.NETWORK_TYPE_NR + || (isLte(rat) && isNrConnected())) { + if (isNrAdvanced()) { + transitionTo(mNrConnectedAdvancedState); + } else { + // Update in case the override network type changed + updateOverrideNetworkType(); + } } else if (isLte(rat) && isNrNotRestricted()) { transitionWithTimerTo(isPhysicalLinkActive() ? mLteConnectedState : mIdleState); @@ -905,34 +935,102 @@ public class NetworkTypeController extends StateMachine { transitionWithTimerTo(mLegacyState); } break; - case EVENT_NR_STATE_CHANGED: - if (isLte(rat) && isNrNotRestricted()) { + case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: + mPhysicalChannelConfigs = + mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + if (isUsingPhysicalChannelConfigForRrcDetection()) { + mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); + } + // Check NR advanced in case NR advanced bands were added + if (isNrAdvanced()) { + transitionTo(mNrConnectedAdvancedState); + } + break; + case EVENT_PHYSICAL_LINK_STATUS_CHANGED: + AsyncResult ar = (AsyncResult) msg.obj; + mPhysicalLinkStatus = (int) ar.result; + break; + default: + return NOT_HANDLED; + } + if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) { + mPreviousState = getName(); + } + return HANDLED; + } + + @Override + public String getName() { + return STATE_CONNECTED; + } + } + + private final NrConnectedState mNrConnectedState = new NrConnectedState(); + + /** + * Device is connected to 5G NR as the primary cell and the data rate is higher than + * the generic 5G data rate. + */ + private final class NrConnectedAdvancedState extends State { + @Override + public void enter() { + if (DBG) log("Entering NrConnectedAdvancedState"); + updateTimers(); + updateOverrideNetworkType(); + if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) { + mPreviousState = getName(); + } + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) log("NrConnectedAdvancedState: process " + getEventName(msg.what)); + updateTimers(); + switch (msg.what) { + case EVENT_SERVICE_STATE_CHANGED: + mServiceState = mPhone.getServiceStateTracker().getServiceState(); + if (DBG) log("ServiceState updated: " + mServiceState); + // fallthrough + case EVENT_UPDATE: + int rat = getDataNetworkType(); + if (rat == TelephonyManager.NETWORK_TYPE_NR + || (isLte(rat) && isNrConnected())) { + if (isNrAdvanced()) { + // Update in case the override network type changed + updateOverrideNetworkType(); + } else { + if (rat == TelephonyManager.NETWORK_TYPE_NR && mOverrideNetworkType + != TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) { + // manually override network type after data rat changes since + // timer will prevent it from being updated + mOverrideNetworkType = + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE; + } + transitionWithTimerTo(mNrConnectedState); + } + } else if (isLte(rat) && isNrNotRestricted()) { transitionWithTimerTo(isPhysicalLinkActive() ? mLteConnectedState : mIdleState); - } else if (rat != TelephonyManager.NETWORK_TYPE_NR && !isNrConnected()) { + } else { transitionWithTimerTo(mLegacyState); } break; - case EVENT_UPDATE_NR_ADVANCED_STATE: - updateNrAdvancedState(); - break; - case EVENT_NR_FREQUENCY_CHANGED: case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: + mPhysicalChannelConfigs = + mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); } - updateNrAdvancedState(); + // Check NR advanced in case NR advanced bands were removed + if (!isNrAdvanced()) { + transitionWithTimerTo(mNrConnectedState); + } break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: AsyncResult ar = (AsyncResult) msg.obj; mPhysicalLinkStatus = (int) ar.result; - if (!isNrConnected()) { - log("NR state changed. Sending EVENT_NR_STATE_CHANGED"); - sendMessage(EVENT_NR_STATE_CHANGED); - } - break; - case EVENT_BANDWIDTH_CHANGED: - updateNrAdvancedState(); break; default: return NOT_HANDLED; @@ -945,37 +1043,15 @@ public class NetworkTypeController extends StateMachine { @Override public String getName() { - return mIsNrAdvanced ? STATE_CONNECTED_NR_ADVANCED : STATE_CONNECTED; - } - - private void updateNrAdvancedState() { - if (!isNrConnected() && getDataNetworkType() != TelephonyManager.NETWORK_TYPE_NR) { - log("NR state changed. Sending EVENT_NR_STATE_CHANGED"); - sendMessage(EVENT_NR_STATE_CHANGED); - return; - } - boolean isNrAdvanced = isNrAdvanced(); - if (isNrAdvanced != mIsNrAdvanced) { - if (!isNrAdvanced) { - if (DBG) log("updateNrAdvancedState: CONNECTED_NR_ADVANCED -> CONNECTED"); - transitionWithTimerTo(mNrConnectedState, STATE_CONNECTED); - } else { - if (DBG) log("updateNrAdvancedState: CONNECTED -> CONNECTED_NR_ADVANCED"); - transitionTo(mNrConnectedState); - } - } - mIsNrAdvanced = isNrAdvanced(); - log("mIsNrAdvanced=" + mIsNrAdvanced); + return STATE_CONNECTED_NR_ADVANCED; } } - private final NrConnectedState mNrConnectedState = new NrConnectedState(); + private final NrConnectedAdvancedState mNrConnectedAdvancedState = + new NrConnectedAdvancedState(); private void transitionWithTimerTo(IState destState) { - transitionWithTimerTo(destState, destState.getName()); - } - - private void transitionWithTimerTo(IState destState, String destName) { + String destName = destState.getName(); if (DBG) log("Transition with primary timer from " + mPreviousState + " to " + destName); OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState); if (!mIsDeviceIdleMode && rule != null && rule.getTimer(destName) > 0) { @@ -1011,22 +1087,23 @@ public class NetworkTypeController extends StateMachine { private void transitionToCurrentState() { int dataRat = getDataNetworkType(); IState transitionState; - if (dataRat == TelephonyManager.NETWORK_TYPE_NR || isNrConnected()) { - transitionState = mNrConnectedState; - mPreviousState = isNrAdvanced() ? STATE_CONNECTED_NR_ADVANCED : STATE_CONNECTED; + if (dataRat == TelephonyManager.NETWORK_TYPE_NR || (isLte(dataRat) && isNrConnected())) { + if (isNrAdvanced()) { + transitionState = mNrConnectedAdvancedState; + } else { + transitionState = mNrConnectedState; + } } else if (isLte(dataRat) && isNrNotRestricted()) { if (isPhysicalLinkActive()) { transitionState = mLteConnectedState; - mPreviousState = STATE_NOT_RESTRICTED_RRC_CON; } else { transitionState = mIdleState; - mPreviousState = STATE_NOT_RESTRICTED_RRC_IDLE; } } else { transitionState = mLegacyState; - mPreviousState = isNrRestricted() ? STATE_RESTRICTED : STATE_LEGACY; } if (!transitionState.equals(getCurrentState())) { + mPreviousState = getCurrentState().getName(); transitionTo(transitionState); } else { updateOverrideNetworkType(); @@ -1073,12 +1150,17 @@ public class NetworkTypeController extends StateMachine { if (currentState.equals(STATE_CONNECTED_NR_ADVANCED)) { if (DBG) log("Reset timers since state is NR_ADVANCED."); resetAllTimers(); - } - - int rat = getDataNetworkType(); - if (!isLte(rat) && rat != TelephonyManager.NETWORK_TYPE_NR) { - if (DBG) log("Reset timers since 2G and 3G don't need NR timers."); + } else if (currentState.equals(STATE_CONNECTED) + && !mPrimaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED) + && !mSecondaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED)) { + if (DBG) log("Reset non-NR_ADVANCED timers since state is NR_CONNECTED"); resetAllTimers(); + } else { + int rat = getDataNetworkType(); + if (!isLte(rat) && rat != TelephonyManager.NETWORK_TYPE_NR) { + if (DBG) log("Reset timers since 2G and 3G don't need NR timers."); + resetAllTimers(); + } } } } @@ -1175,17 +1257,15 @@ public class NetworkTypeController extends StateMachine { } private boolean isNrConnected() { - return mPhone.getServiceState().getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED; + return mServiceState.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED; } private boolean isNrNotRestricted() { - return mPhone.getServiceState().getNrState() - == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED; + return mServiceState.getNrState() == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED; } private boolean isNrRestricted() { - return mPhone.getServiceState().getNrState() - == NetworkRegistrationInfo.NR_STATE_RESTRICTED; + return mServiceState.getNrState() == NetworkRegistrationInfo.NR_STATE_RESTRICTED; } /** @@ -1200,7 +1280,7 @@ public class NetworkTypeController extends StateMachine { // Check if NR advanced is enabled when the device is roaming. Some carriers disable it // while the device is roaming. - if (mPhone.getServiceState().getDataRoaming() && !mEnableNrAdvancedWhileRoaming) { + if (mServiceState.getDataRoaming() && !mEnableNrAdvancedWhileRoaming) { return false; } @@ -1214,6 +1294,7 @@ public class NetworkTypeController extends StateMachine { .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) { @@ -1226,18 +1307,15 @@ public class NetworkTypeController extends StateMachine { } private boolean isNrMmwave() { - return mPhone.getServiceState().getNrFrequencyRange() - == ServiceState.FREQUENCY_RANGE_MMWAVE; + return mServiceState.getNrFrequencyRange() == ServiceState.FREQUENCY_RANGE_MMWAVE; } private boolean isAdditionalNrAdvancedBand() { - List physicalChannelConfigList = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); if (ArrayUtils.isEmpty(mAdditionalNrAdvancedBandsList) - || physicalChannelConfigList == null) { + || mPhysicalChannelConfigs == null) { return false; } - for (PhysicalChannelConfig item : physicalChannelConfigList) { + for (PhysicalChannelConfig item : mPhysicalChannelConfigs) { if (item.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR && ArrayUtils.contains(mAdditionalNrAdvancedBandsList, item.getBand())) { return true; @@ -1256,19 +1334,10 @@ public class NetworkTypeController extends StateMachine { } private int getPhysicalLinkStatusFromPhysicalChannelConfig() { - List physicalChannelConfigList = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); - return (physicalChannelConfigList == null || physicalChannelConfigList.isEmpty()) + return (mPhysicalChannelConfigs == null || mPhysicalChannelConfigs.isEmpty()) ? DataCallResponse.LINK_STATUS_DORMANT : DataCallResponse.LINK_STATUS_ACTIVE; } - private int getDataNetworkType() { - NetworkRegistrationInfo nri = mPhone.getServiceState().getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - return nri == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN - : nri.getAccessNetworkTechnology(); - } - private String getEventName(int event) { try { return sEvents[event]; diff --git a/src/java/com/android/internal/telephony/NitzSignal.java b/src/java/com/android/internal/telephony/NitzSignal.java index 889fe95c7c6ac811f5eadc045e333483a18715b4..2619f3da439988736e890779c0da913424df24e0 100644 --- a/src/java/com/android/internal/telephony/NitzSignal.java +++ b/src/java/com/android/internal/telephony/NitzSignal.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.annotation.DurationMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; -import android.os.TimestampedValue; +import android.app.time.UnixEpochTime; import java.time.Duration; import java.util.Objects; @@ -88,13 +88,12 @@ public final class NitzSignal { } /** - * Creates a {@link android.os.TimestampedValue} containing the UTC time as the number of - * milliseconds since the start of the Unix epoch. The reference time is the time according to - * the elapsed realtime clock when that would have been the time, accounting for receipt time - * and age. + * Creates a {@link UnixEpochTime} containing the UTC time as the number of milliseconds since + * the start of the Unix epoch. The reference time is the time according to the elapsed realtime + * clock when that would have been the time, accounting for receipt time and age. */ - public TimestampedValue createTimeSignal() { - return new TimestampedValue<>( + public UnixEpochTime createTimeSignal() { + return new UnixEpochTime( getAgeAdjustedElapsedRealtimeMillis(), getNitzData().getCurrentTimeInMillis()); } diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java index 5ce33c2fb1d0c05a111a0fa7c793829193f4482b..4e62d20eb2297c89ac86c83e2ef02e69b0ec97ae 100644 --- a/src/java/com/android/internal/telephony/Phone.java +++ b/src/java/com/android/internal/telephony/Phone.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.BroadcastOptions; @@ -24,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.hardware.radio.modem.ImeiInfo; import android.net.Uri; import android.os.AsyncResult; import android.os.Build; @@ -35,17 +38,21 @@ import android.os.Registrant; import android.os.RegistrantList; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; import android.os.WorkSource; import android.preference.PreferenceManager; -import android.provider.DeviceConfig; import android.sysprop.TelephonyProperties; import android.telecom.VideoProfile; import android.telephony.AccessNetworkConstants; +import android.telephony.Annotation.SrvccState; import android.telephony.CarrierConfigManager; import android.telephony.CarrierRestrictionRules; +import android.telephony.CellBroadcastIdRange; import android.telephony.CellIdentity; import android.telephony.CellInfo; import android.telephony.ClientRequestStats; +import android.telephony.DomainSelectionService; import android.telephony.ImsiEncryptionInfo; import android.telephony.LinkCapacityEstimate; import android.telephony.NetworkRegistrationInfo; @@ -60,9 +67,12 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.HalService; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RegistrationManager; +import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; +import android.telephony.satellite.SatelliteDatagram; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -78,7 +88,11 @@ import com.android.internal.telephony.data.AccessNetworksManager; import com.android.internal.telephony.data.DataNetworkController; import com.android.internal.telephony.data.DataSettingsManager; import com.android.internal.telephony.data.LinkBandwidthEstimator; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; +import com.android.internal.telephony.emergency.EmergencyConstants; import com.android.internal.telephony.emergency.EmergencyNumberTracker; +import com.android.internal.telephony.emergency.EmergencyStateTracker; +import com.android.internal.telephony.imsphone.ImsCallInfo; import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.imsphone.ImsPhoneCall; import com.android.internal.telephony.metrics.SmsStats; @@ -168,7 +182,8 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { protected static final int EVENT_RADIO_ON = 5; protected static final int EVENT_GET_BASEBAND_VERSION_DONE = 6; protected static final int EVENT_USSD = 7; - protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 8; + @VisibleForTesting + public static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 8; private static final int EVENT_GET_SIM_STATUS_DONE = 11; protected static final int EVENT_SET_CALL_FORWARD_DONE = 12; protected static final int EVENT_GET_CALL_FORWARD_DONE = 13; @@ -232,8 +247,12 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { protected static final int EVENT_SUBSCRIPTIONS_CHANGED = 62; protected static final int EVENT_GET_USAGE_SETTING_DONE = 63; protected static final int EVENT_SET_USAGE_SETTING_DONE = 64; + protected static final int EVENT_IMS_DEREGISTRATION_TRIGGERED = 65; + protected static final int EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE = 66; + protected static final int EVENT_GET_DEVICE_IMEI_DONE = 67; + protected static final int EVENT_TRIGGER_NOTIFY_ANBR = 68; - protected static final int EVENT_LAST = EVENT_SET_USAGE_SETTING_DONE; + protected static final int EVENT_LAST = EVENT_TRIGGER_NOTIFY_ANBR; // For shared prefs. private static final String GSM_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_roaming_list_"; @@ -260,6 +279,10 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { // Integer used to let the calling application know that the we are ignoring auto mode switch. private static final int ALREADY_IN_AUTO_SELECTION = 1; + public static final String PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED = + "pref_null_cipher_and_integrity_enabled"; + private final TelephonyAdminReceiver m2gAdminUpdater; + /** * This method is invoked when the Phone exits Emergency Callback Mode. */ @@ -328,7 +351,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected AtomicReference mUiccApplication = new AtomicReference(); - TelephonyTester mTelephonyTester; + private TelephonyTester mTelephonyTester; private String mName; private final String mActionDetached; private final String mActionAttached; @@ -357,12 +380,6 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { private int mUsageSettingFromModem = SubscriptionManager.USAGE_SETTING_UNKNOWN; private boolean mIsUsageSettingSupported = true; - /** - * {@code true} if the new SubscriptionManagerService is enabled, otherwise the old - * SubscriptionController is used. - */ - private boolean mIsSubscriptionManagerServiceEnabled = false; - //IMS /** * {@link CallStateException} message text used to indicate that an IMS call has failed because @@ -391,7 +408,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { public static final String EXTRA_KEY_ALERT_SHOW = "alertShow"; public static final String EXTRA_KEY_NOTIFICATION_MESSAGE = "notificationMessage"; - private final RegistrantList mPreciseCallStateRegistrants = new RegistrantList(); + protected final RegistrantList mPreciseCallStateRegistrants = new RegistrantList(); private final RegistrantList mHandoverRegistrants = new RegistrantList(); @@ -466,6 +483,10 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { protected LinkBandwidthEstimator mLinkBandwidthEstimator; + public static final int IMEI_TYPE_UNKNOWN = -1; + public static final int IMEI_TYPE_PRIMARY = ImeiInfo.ImeiType.PRIMARY; + public static final int IMEI_TYPE_SECONDARY = ImeiInfo.ImeiType.SECONDARY; + public IccRecords getIccRecords() { return mIccRecords.get(); } @@ -603,14 +624,8 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { // Initialize SMS stats mSmsStats = new SmsStats(this); - // This is a temp flag which will be removed before U AOSP public release. - mIsSubscriptionManagerServiceEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_using_subscription_manager_service) - || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY, - "enable_subscription_manager_service", false); - if (isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService = SubscriptionManagerService.getInstance(); - } + mSubscriptionManagerService = SubscriptionManagerService.getInstance(); + m2gAdminUpdater = new TelephonyAdminReceiver(context, this); if (getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { return; @@ -860,7 +875,11 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { return null; } - public void notifySrvccState(Call.SrvccState state) { + /** + * Notifies the change of the SRVCC state. + * @param state the new SRVCC state. + */ + public void notifySrvccState(@SrvccState int state) { } public void registerForSilentRedial(Handler h, int what, Object obj) { @@ -883,6 +902,9 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { Call.SrvccState srvccState = Call.SrvccState.NONE; if (ret != null && ret.length != 0) { int state = ret[0]; + if (imsPhone != null) { + imsPhone.notifySrvccState(state); + } switch(state) { case TelephonyManager.SRVCC_STATE_HANDOVER_STARTED: srvccState = Call.SrvccState.STARTED; @@ -895,11 +917,6 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { break; case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED: srvccState = Call.SrvccState.COMPLETED; - if (imsPhone != null) { - imsPhone.notifySrvccState(srvccState); - } else { - Rlog.d(LOG_TAG, "HANDOVER_COMPLETED: mImsPhone null"); - } break; case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED: case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED: @@ -973,17 +990,6 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { mPreciseCallStateRegistrants.remove(h); } - /** - * Subclasses of Phone probably want to replace this with a - * version scoped to their packages - */ - protected void notifyPreciseCallStateChangedP() { - AsyncResult ar = new AsyncResult(null, this, null); - mPreciseCallStateRegistrants.notifyRegistrants(ar); - - mNotifier.notifyPreciseCallState(this); - } - /** * Notifies when a Handover happens due to SRVCC or Silent Redial */ @@ -1431,8 +1437,8 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { @UnsupportedAppUsage public void setNetworkSelectionModeAutomatic(Message response) { Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic, querying current mode"); - // we don't want to do this unecesarily - it acutally causes - // the radio to repeate network selection and is costly + // we don't want to do this unnecessarily - it actually causes + // the radio to repeat network selection and is costly // first check if we're already in automatic mode Message msg = obtainMessage(EVENT_CHECK_FOR_NETWORK_AUTOMATIC); msg.obj = response; @@ -1465,6 +1471,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { nsm.operatorAlphaShort = ""; if (doAutomatic) { + Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - set network selection auto"); Message msg = obtainMessage(EVENT_SET_NETWORK_AUTOMATIC_COMPLETE, nsm); mCi.setNetworkSelectionModeAutomatic(msg); } else { @@ -1921,7 +1928,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { public boolean isRadioOffForThermalMitigation() { ServiceStateTracker sst = getServiceStateTracker(); return sst != null && sst.getRadioPowerOffReasons() - .contains(Phone.RADIO_POWER_REASON_THERMAL); + .contains(TelephonyManager.RADIO_POWER_REASON_THERMAL); } /** @@ -2308,6 +2315,12 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { if (!mIsCarrierNrSupported) { allowedNetworkTypes &= ~TelephonyManager.NETWORK_TYPE_BITMASK_NR; } + if (m2gAdminUpdater.isCellular2gDisabled()) { + logd("SubId " + getSubId() + + " disabling 2g in getEffectiveAllowedNetworkTypes according to admin user " + + "restriction"); + allowedNetworkTypes &= ~TelephonyManager.NETWORK_CLASS_BITMASK_2G; + } logd("SubId" + getSubId() + ",getEffectiveAllowedNetworkTypes: " + TelephonyManager.convertNetworkTypeBitmaskToString(allowedNetworkTypes)); return allowedNetworkTypes; @@ -2384,21 +2397,10 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { */ public void loadAllowedNetworksFromSubscriptionDatabase() { String result = null; - if (isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(getSubId()); - if (subInfo != null) { - result = subInfo.getAllowedNetworkTypesForReasons(); - } - } else { - // Try to load ALLOWED_NETWORK_TYPES from SIMINFO. - if (SubscriptionController.getInstance() == null) { - return; - } - - result = SubscriptionController.getInstance().getSubscriptionProperty( - getSubId(), - SubscriptionManager.ALLOWED_NETWORK_TYPES); + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(getSubId()); + if (subInfo != null) { + result = subInfo.getAllowedNetworkTypesForReasons(); } // After fw load network type from DB, do unlock if subId is valid. @@ -2415,14 +2417,14 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { try { // Format: "REASON=VALUE,REASON2=VALUE2" for (String pair : result.trim().split(",")) { - String[] networkTypesValues = (pair.trim().toLowerCase()).split("="); + String[] networkTypesValues = (pair.trim().toLowerCase(Locale.ROOT)).split("="); if (networkTypesValues.length != 2) { Rlog.e(LOG_TAG, "Invalid ALLOWED_NETWORK_TYPES from DB, value = " + pair); continue; } int key = convertAllowedNetworkTypeDbNameToMapIndex(networkTypesValues[0]); long value = Long.parseLong(networkTypesValues[1]); - if (key != INVALID_ALLOWED_NETWORK_TYPES + if (TelephonyManager.isValidAllowedNetworkTypesReason(key) && value != INVALID_ALLOWED_NETWORK_TYPES) { synchronized (mAllowedNetworkTypesForReasons) { mAllowedNetworkTypesForReasons.put(key, value); @@ -2479,7 +2481,10 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G: return ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G; default: - return Integer.toString(INVALID_ALLOWED_NETWORK_TYPES); + throw new IllegalArgumentException( + "No DB name conversion available for allowed network type reason: " + reason + + ". Did you forget to add an ALLOWED_NETWORK_TYPE_TEXT entry for" + + " a new reason?"); } } @@ -2979,6 +2984,9 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { // This property is used to handle phone process crashes, and is the same for CDMA and IMS // phones protected static boolean getInEcmMode() { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + return EmergencyStateTracker.getInstance().isInEcm(); + } return TelephonyProperties.in_ecm_mode().orElse(false); } @@ -2988,6 +2996,9 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { * emergency operator. */ public boolean isInEcm() { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + return EmergencyStateTracker.getInstance().isInEcm(); + } return mIsPhoneInEcmState; } @@ -2996,6 +3007,9 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { } public boolean isInCdmaEcm() { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + return EmergencyStateTracker.getInstance().isInCdmaEcm(); + } return getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA && isInEcm() && (mImsPhone == null || !mImsPhone.isInImsEcm()); } @@ -4081,17 +4095,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { */ @UnsupportedAppUsage public int getSubId() { - if (isSubscriptionManagerServiceEnabled()) { - return mSubscriptionManagerService.getSubId(mPhoneId); - } - if (SubscriptionController.getInstance() == null) { - // TODO b/78359408 getInstance sometimes returns null in Treehugger tests, which causes - // flakiness. Even though we haven't seen this crash in the wild we should keep this - // check in until we've figured out the root cause. - Rlog.e(LOG_TAG, "SubscriptionController.getInstance = null! Returning default subId"); - return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; - } - return SubscriptionController.getInstance().getSubId(mPhoneId); + return mSubscriptionManagerService.getSubId(mPhoneId); } /** @@ -4387,6 +4391,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { // When radio capability switch is done, query IMEI value and update it in Phone objects // to make it in sync with the IMEI value currently used by Logical-Modem. if (capabilitySwitched) { + mCi.getImei(obtainMessage(EVENT_GET_DEVICE_IMEI_DONE)); mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE)); } } @@ -4406,14 +4411,10 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { private int getResolvedUsageSetting(int subId) { SubscriptionInfo subInfo = null; - if (isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfoInternal = mSubscriptionManagerService - .getSubscriptionInfoInternal(subId); - if (subInfoInternal != null) { - subInfo = subInfoInternal.toSubscriptionInfo(); - } - } else { - subInfo = SubscriptionController.getInstance().getSubscriptionInfo(subId); + SubscriptionInfoInternal subInfoInternal = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + if (subInfoInternal != null) { + subInfo = subInfoInternal.toSubscriptionInfo(); } if (subInfo == null @@ -4750,10 +4751,24 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { * Get the HAL version. * * @return the current HalVersion + * + * @deprecated Use {@link #getHalVersion(int service)} instead. */ + @Deprecated public HalVersion getHalVersion() { + return getHalVersion(HAL_SERVICE_RADIO); + } + + /** + * Get the HAL version with a specific service. + * + * @param service the service id to query + * @return the current HalVersion for a specific service + * + */ + public HalVersion getHalVersion(@HalService int service) { if (mCi != null && mCi instanceof RIL) { - return ((RIL) mCi).getHalVersion(); + return ((RIL) mCi).getHalVersion(service); } return RIL.RADIO_HAL_VERSION_UNKNOWN; } @@ -4897,11 +4912,600 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { } /** - * @return {@code true} if the new {@link SubscriptionManagerService} is enabled, otherwise the - * old {@link SubscriptionController} is used. + * Returns the user's last setting for terminal-based call waiting + * @param forCsOnly indicates the caller expects the result for CS calls only + */ + public int getTerminalBasedCallWaitingState(boolean forCsOnly) { + return CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED; + } + + /** + * Notifies the change of the user setting of the terminal-based call waiting service + * to IMS service. + */ + public void setTerminalBasedCallWaitingStatus(int state) { + } + + /** + * Notifies that the IMS service connected supports the terminal-based call waiting service + */ + public void setTerminalBasedCallWaitingSupported(boolean supported) { + } + + /** + * Notifies the NAS and RRC layers of the radio the type of upcoming IMS traffic. + * + * @param token A nonce to identify the request. + * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc. + * @param accessNetworkType The type of the radio access network used. + * @param trafficDirection Indicates whether traffic is originated by mobile originated or + * mobile terminated use case eg. MO/MT call/SMS etc. + * @param response is callback message. + */ + public void startImsTraffic(int token, + @MmTelFeature.ImsTrafficType int trafficType, + @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType, + @MmTelFeature.ImsTrafficDirection int trafficDirection, Message response) { + mCi.startImsTraffic(token, trafficType, accessNetworkType, trafficDirection, response); + } + + /** + * Notifies IMS traffic has been stopped. + * + * @param token The token assigned by startImsTraffic. + * @param response is callback message. + */ + public void stopImsTraffic(int token, Message response) { + mCi.stopImsTraffic(token, response); + } + + /** + * Register for notifications of connection setup failure + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForConnectionSetupFailure(Handler h, int what, Object obj) { + mCi.registerForConnectionSetupFailure(h, what, obj); + } + + /** + * Unregister for notifications of connection setup failure + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForConnectionSetupFailure(Handler h) { + mCi.unregisterForConnectionSetupFailure(h); + } + + /** + * Triggers the UE initiated EPS fallback procedure. + * + * @param reason specifies the reason for EPS fallback. + * @param response is callback message. + */ + public void triggerEpsFallback(@MmTelFeature.EpsFallbackReason int reason, Message response) { + mCi.triggerEpsFallback(reason, response); + } + + /** + * Notifies the recommended bit rate for the indicated logical channel and direction. + * + * @param mediaType MediaType is used to identify media stream such as audio or video. + * @param direction Direction of this packet stream (e.g. uplink or downlink). + * @param bitsPerSecond The recommended bit rate for the UE for a specific logical channel and + * a specific direction by NW. + */ + public void triggerNotifyAnbr(int mediaType, int direction, int bitsPerSecond) { + } + + /** + * Sets the emergency mode + * + * @param emcMode The radio emergency mode type. + * @param result Callback message. + */ + public void setEmergencyMode(@EmergencyConstants.EmergencyMode int emcMode, + @Nullable Message result) { + mCi.setEmergencyMode(emcMode, result); + } + + /** + * Triggers an emergency network scan. + * + * @param accessNetwork Contains the list of access network types to be prioritized + * during emergency scan. The 1st entry has the highest priority. + * @param scanType Indicates the type of scans to be performed i.e. limited scan, + * full service scan or any scan. + * @param result Callback message. + */ + public void triggerEmergencyNetworkScan( + @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork, + @DomainSelectionService.EmergencyScanType int scanType, @Nullable Message result) { + mCi.triggerEmergencyNetworkScan(accessNetwork, scanType, result); + } + + /** + * Cancels ongoing emergency network scan + * @param resetScan Indicates how the next {@link #triggerEmergencyNetworkScan} should work. + * If {@code true}, then the modem shall start the new scan from the beginning, + * otherwise the modem shall resume from the last search. + * @param result Callback message. + */ + public void cancelEmergencyNetworkScan(boolean resetScan, @Nullable Message result) { + mCi.cancelEmergencyNetworkScan(resetScan, result); + } + + /** + * Exits ongoing emergency mode + * @param result Callback message. + */ + public void exitEmergencyMode(@Nullable Message result) { + mCi.exitEmergencyMode(result); + } + + /** + * Registers for emergency network scan result. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForEmergencyNetworkScan(@NonNull Handler h, + int what, @Nullable Object obj) { + mCi.registerForEmergencyNetworkScan(h, what, obj); + } + + /** + * Unregisters for emergency network scan result. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForEmergencyNetworkScan(@NonNull Handler h) { + mCi.unregisterForEmergencyNetworkScan(h); + } + + /** + * Notifies that IMS deregistration is triggered. + * + * @param reason the reason why the deregistration is triggered. + */ + public void triggerImsDeregistration( + @ImsRegistrationImplBase.ImsDeregistrationReason int reason) { + if (mImsPhone != null) { + mImsPhone.triggerImsDeregistration(reason); + } + } + + /** + * Registers for the domain selected for emergency calls. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForEmergencyDomainSelected( + @NonNull Handler h, int what, @Nullable Object obj) { + } + + /** + * Unregisters for the domain selected for emergency calls. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForEmergencyDomainSelected(@NonNull Handler h) { + } + + /** + * Notifies the domain selected. + * + * @param transportType The preferred transport type. + */ + public void notifyEmergencyDomainSelected( + @AccessNetworkConstants.TransportType int transportType) { + } + + /** + * @return Telephony tester instance. + */ + public @Nullable TelephonyTester getTelephonyTester() { + return mTelephonyTester; + } + + /** + * @return User handle associated with the phone's subscription id. {@code null} if subscription + * is invalid or not found. + */ + @Nullable + public UserHandle getUserHandle() { + int subId = getSubId(); + + UserHandle userHandle = null; + try { + SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); + if (subManager != null) { + userHandle = subManager.getSubscriptionUserHandle(subId); + } + } catch (IllegalArgumentException ex) { + loge("getUserHandle: ex=" + ex); + } + + return userHandle; + } + + /** + * Checks if the context user is a managed profile. + * + * Note that this applies specifically to managed profiles. + * + * @return whether the context user is a managed profile. + */ + public boolean isManagedProfile() { + UserHandle userHandle = getUserHandle(); + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userHandle == null || userManager == null) return false; + return userManager.isManagedProfile(userHandle.getIdentifier()); + } + + /** + * @return global null cipher and integrity enabled preference + */ + public boolean getNullCipherAndIntegrityEnabledPreference() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + return sp.getBoolean(PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED, true); + } + + /** + * @return whether or not this Phone interacts with a modem that supports the null cipher + * and integrity feature. + */ + public boolean isNullCipherAndIntegritySupported() { + return false; + } + + /** + * Override to implement handling of an update to the enablement of the null cipher and + * integrity preference. + * {@see #PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED} + */ + public void handleNullCipherEnabledChange() { + } + + /** + * Notifies the IMS call status to the modem. + * + * @param imsCallInfo The list of {@link ImsCallInfo}. + * @param response A callback to receive the response. + */ + public void updateImsCallStatus(@NonNull List imsCallInfo, Message response) { + mCi.updateImsCallStatus(imsCallInfo, response); + } + + /** + * Enables or disables N1 mode (access to 5G core network) in accordance with + * 3GPP TS 24.501 4.9. + * @param enable {@code true} to enable N1 mode, {@code false} to disable N1 mode. + * @param result Callback message to receive the result. + */ + public void setN1ModeEnabled(boolean enable, Message result) { + mCi.setN1ModeEnabled(enable, result); + } + + /** + * Check whether N1 mode (access to 5G core network) is enabled or not. + * @param result Callback message to receive the result. + */ + public void isN1ModeEnabled(Message result) { + mCi.isN1ModeEnabled(result); + } + + /** + * Return current cell broadcast ranges. + */ + public List getCellBroadcastIdRanges() { + return new ArrayList<>(); + } + + /** + * Set reception of cell broadcast messages with the list of the given ranges. + */ + public void setCellBroadcastIdRanges( + @NonNull List ranges, Consumer callback) { + callback.accept(TelephonyManager.CELL_BROADCAST_RESULT_UNSUPPORTED); + } + + /** + * Start receiving satellite position updates. + * This can be called by the pointing UI when the user starts pointing to the satellite. + * Modem should continue to report the pointing input as the device or satellite moves. + * + * @param result The Message to send to result of the operation to. + **/ + public void startSatellitePositionUpdates(Message result) { + mCi.startSendingSatellitePointingInfo(result); + } + + /** + * Stop receiving satellite position updates. + * This can be called by the pointing UI when the user stops pointing to the satellite. + * + * @param result The Message to send to result of the operation to. + **/ + public void stopSatellitePositionUpdates(Message result) { + mCi.stopSendingSatellitePointingInfo(result); + } + + /** + * Get maximum number of characters per text message on satellite. + * @param result The Message to send the result of the operation to. + */ + public void getMaxCharactersPerSatelliteTextMessage(Message result) { + mCi.getMaxCharactersPerSatelliteTextMessage(result); + } + + /** + * Power on or off the satellite modem. + * @param result The Message to send the result of the operation to. + * @param powerOn {@code true} to power on the satellite modem and {@code false} to power off. + */ + public void setSatellitePower(Message result, boolean powerOn) { + mCi.setSatellitePower(result, powerOn); + } + + /** + * Check whether the satellite modem is powered on. + * @param result The Message to send the result of the operation to. + */ + public void isSatellitePowerOn(Message result) { + mCi.getSatellitePowerState(result); + } + + /** + * Check whether the satellite service is supported on the device. + * @param result The Message to send the result of the operation to. + */ + public void isSatelliteSupported(Message result) { + mCi.isSatelliteSupported(result); + } + + /** + * Check whether the satellite modem is provisioned. + * @param result The Message to send the result of the operation to. + */ + public void isSatelliteProvisioned(Message result) { + mCi.getSatelliteProvisionState(result); + } + + /** + * Get the satellite capabilities. + * @param result The Message to send the result of the operation to. + */ + public void getSatelliteCapabilities(Message result) { + mCi.getSatelliteCapabilities(result); + } + + /** + * Registers for pointing info changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatellitePositionInfoChanged(@NonNull Handler h, + int what, @Nullable Object obj) { + //TODO: Rename CommandsInterface and other modules when updating HAL APIs. + mCi.registerForSatellitePointingInfoChanged(h, what, obj); + } + + /** + * Unregisters for pointing info changed from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSatellitePositionInfoChanged(@NonNull Handler h) { + //TODO: Rename CommandsInterface and other modules when updating HAL APIs. + mCi.unregisterForSatellitePointingInfoChanged(h); + } + + /** + * Registers for datagrams delivered events from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatelliteDatagramsDelivered(@NonNull Handler h, + int what, @Nullable Object obj) { + //TODO: Remove. + mCi.registerForSatelliteMessagesTransferComplete(h, what, obj); + } + + /** + * Unregisters for datagrams delivered events from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSatelliteDatagramsDelivered(@NonNull Handler h) { + //TODO: Remove. + mCi.unregisterForSatelliteMessagesTransferComplete(h); + } + + /** + * Provision the subscription with a satellite provider. + * This is needed to register the device/subscription if the provider allows dynamic + * registration. + * + * @param result Callback message to receive the result. + * @param token The token of the device/subscription to be provisioned. + */ + public void provisionSatelliteService(Message result, String token) { + // TODO: update parameters in HAL + // mCi.provisionSatelliteService(result, token); + } + + /** + * Deprovision the device/subscription with a satellite provider. + * This is needed to unregister the device/subscription if the provider allows dynamic + * registration. + * If provisioning is in progress for the given SIM, cancel the request. + * If there is no request in progress, deprovision the given SIM. + * + * @param result Callback message to receive the result. + * @param token The token of the device/subscription to be deprovisioned. + */ + public void deprovisionSatelliteService(Message result, String token) { + //TODO (b/266126070): add implementation. + } + + /** + * Register for a satellite provision state changed event. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatelliteProvisionStateChanged(Handler h, int what, Object obj) { + mCi.registerForSatelliteProvisionStateChanged(h, what, obj); + } + + /** + * Unregister for a satellite provision state changed event. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSatelliteProvisionStateChanged(Handler h) { + mCi.unregisterForSatelliteProvisionStateChanged(h); + } + + /** + * Get the list of provisioned satellite features. + * + * @param result Callback message to receive the result. + */ + public void getProvisionedSatelliteFeatures(Message result) { + //TODO (b/266126070): add implementation. + } + + /** + * Registers for satellite state changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatelliteModemStateChanged(@NonNull Handler h, int what, + @Nullable Object obj) { + mCi.registerForSatelliteModeChanged(h, what, obj); + } + + /** + * Unregisters for satellite state changed from satellite modem. + * + * @param h Handler to be removed from registrant list. + */ + public void unregisterForSatelliteModemStateChanged(@NonNull Handler h) { + mCi.unregisterForSatelliteModeChanged(h); + } + + /** + * Registers for pending datagram count info from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForPendingDatagramCount(@NonNull Handler h, int what, + @Nullable Object obj) { + mCi.registerForPendingSatelliteMessageCount(h, what, obj); + } + + /** + * Unregisters for pending datagram count info from satellite modem. + * + * @param h Handler to be removed from registrant list. + */ + public void unregisterForPendingDatagramCount(@NonNull Handler h) { + mCi.unregisterForPendingSatelliteMessageCount(h); + } + + /** + * Register to receive incoming datagrams over satellite. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatelliteDatagramsReceived(@NonNull Handler h, int what, + @Nullable Object obj) { + // TODO: rename + mCi.registerForNewSatelliteMessages(h, what, obj); + } + + /** + * Unregister to stop receiving incoming datagrams over satellite. + * + * @param h Handler to be removed from registrant list. + */ + public void unregisterForSatelliteDatagramsReceived(@NonNull Handler h) { + // TODO: rename + mCi.unregisterForNewSatelliteMessages(h); + } + + /** + * Poll pending datagrams over satellite. + * @param result The Message to send the result of the operation to. + */ + public void pollPendingSatelliteDatagrams(Message result) { + //mCi.pollPendingSatelliteDatagrams(result); + } + + /** + * Send datagram over satellite. + * @param result The Message to send the result of the operation to. + * @param datagram Datagram to send over satellite. + * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in + * full screen mode. + */ + public void sendSatelliteDatagram(Message result, SatelliteDatagram datagram, + boolean needFullScreenPointingUI) { + //mCi.sendSatelliteDatagram(result, datagram); + } + + /** + * Check whether satellite communication is allowed for the current location. + * @param result The Message to send the result of the operation to. + */ + public void isSatelliteCommunicationAllowedForCurrentLocation(Message result) { + mCi.isSatelliteCommunicationAllowedForCurrentLocation(result); + } + + /** + * Get the time after which the satellite will be visible. + * @param result The Message to send the result of the operation to. + */ + public void requestTimeForNextSatelliteVisibility(Message result) { + mCi.getTimeForNextSatelliteVisibility(result); + } + + /** + * Start callback mode + * @param type for callback mode entry. + */ + public void startCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type) { + Rlog.d(LOG_TAG, "startCallbackMode:type=" + type); + mNotifier.notifyCallbackModeStarted(this, type); + } + + /** + * Stop callback mode + * @param type for callback mode exit. + * @param reason for stopping callback mode. */ - public boolean isSubscriptionManagerServiceEnabled() { - return mIsSubscriptionManagerServiceEnabled; + public void stopCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type, + @TelephonyManager.EmergencyCallbackModeStopReason int reason) { + Rlog.d(LOG_TAG, "stopCallbackMode:type=" + type + ", reason=" + reason); + mNotifier.notifyCallbackModeStopped(this, type, reason); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -4926,7 +5530,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { pw.println(" isDnsCheckDisabled()=" + isDnsCheckDisabled()); pw.println(" getUnitTestMode()=" + getUnitTestMode()); pw.println(" getState()=" + getState()); - pw.println(" getIccSerialNumber()=" + getIccSerialNumber()); + pw.println(" getIccSerialNumber()=" + Rlog.pii(LOG_TAG, getIccSerialNumber())); pw.println(" getIccRecordsLoaded()=" + getIccRecordsLoaded()); pw.println(" getMessageWaitingIndicator()=" + getMessageWaitingIndicator()); pw.println(" getCallForwardingIndicator()=" + getCallForwardingIndicator()); diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java index 081e7a0f2031e6fc21a743bcbeeb78cf5add54d5..8b9582419ede852ed483c9c70f4bf367f96f663c 100644 --- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java +++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java @@ -72,6 +72,7 @@ public class PhoneConfigurationManager { private TelephonyManager mTelephonyManager; 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"; private static final boolean DEBUG = !"user".equals(Build.TYPE); /** * Init method to instantiate the object @@ -123,6 +124,21 @@ 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) { + return new PhoneCapability.Builder(staticCapability) + .setMaxActiveVoiceSubscriptions(2) + .build(); + } else { + return staticCapability; + } + } + /** * Static method to get instance. */ @@ -181,6 +197,8 @@ 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); @@ -367,11 +385,7 @@ public class PhoneConfigurationManager { // eg if we are going from 2 phones to 1 phone, we need to deregister RIL for the // second phone. This loop does nothing if numOfActiveModems is increasing. for (int phoneId = numOfActiveModems; phoneId < oldNumOfActiveModems; phoneId++) { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().markSubscriptionsInactive(phoneId); - } else { - SubscriptionController.getInstance().clearSubInfoRecord(phoneId); - } + SubscriptionManagerService.getInstance().markSubscriptionsInactive(phoneId); subInfoCleared = true; mPhones[phoneId].mCi.onSlotActiveStatusChange( SubscriptionManager.isValidPhoneId(phoneId)); @@ -405,13 +419,8 @@ public class PhoneConfigurationManager { + "setting VOICE & SMS subId to -1 (No Preference)"); //Set the default VOICE subId to -1 ("No Preference") - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().setDefaultVoiceSubId( - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - } else { - SubscriptionController.getInstance().setDefaultVoiceSubId( - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - } + SubscriptionManagerService.getInstance().setDefaultVoiceSubId( + SubscriptionManager.INVALID_SUBSCRIPTION_ID); //TODO:: Set the default SMS sub to "No Preference". Tracking this bug (b/227386042) } else { @@ -475,34 +484,46 @@ public class PhoneConfigurationManager { * @return true if the modem service is set successfully, false otherwise. */ public boolean setModemService(String serviceName) { - if (mRadioConfig == null || mPhones[0] == null) { - return false; - } - log("setModemService: " + serviceName); boolean statusRadioConfig = false; boolean statusRil = false; final boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false); + final boolean isAllowedForBoot = + SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false); - // Check for ALLOW_MOCK_MODEM_PROPERTY on user builds - if (isAllowed || DEBUG) { - if (serviceName != null) { + // Check for ALLOW_MOCK_MODEM_PROPERTY and BOOT_ALLOW_MOCK_MODEM_PROPERTY on user builds + if (isAllowed || isAllowedForBoot || DEBUG) { + if (mRadioConfig != null) { statusRadioConfig = mRadioConfig.setModemService(serviceName); + } - //TODO: consider multi-sim case (b/210073692) - statusRil = mPhones[0].mCi.setModemService(serviceName); - } else { - statusRadioConfig = mRadioConfig.setModemService(null); - - //TODO: consider multi-sim case - statusRil = mPhones[0].mCi.setModemService(null); + if (!statusRadioConfig) { + loge("setModemService: switching modem service for radioconfig fail"); + return false; } - return statusRadioConfig && statusRil; + for (int i = 0; i < getPhoneCount(); i++) { + if (mPhones[i] != null) { + statusRil = mPhones[i].mCi.setModemService(serviceName); + } + + if (!statusRil) { + loge("setModemService: switch modem for radio " + i + " fail"); + + // Disconnect the switched service + mRadioConfig.setModemService(null); + for (int t = 0; t < i; t++) { + mPhones[t].mCi.setModemService(null); + } + return false; + } + } } else { loge("setModemService is not allowed"); return false; } + + return true; } /** @@ -510,7 +531,6 @@ public class PhoneConfigurationManager { * @return the service name of the connected service. */ public String getModemService() { - //TODO: consider multi-sim case if (mPhones[0] == null) { return ""; } diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java index 8220ee3ce8dcf87b53c57820a7c6fff4011a6526..57a375b9b43a29c612cdbdef3f9d4bf17a6c8715 100644 --- a/src/java/com/android/internal/telephony/PhoneFactory.java +++ b/src/java/com/android/internal/telephony/PhoneFactory.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO; + import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA; import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA_LTE; @@ -29,10 +31,8 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.LocalServerSocket; import android.os.Build; -import android.os.HandlerThread; import android.os.Looper; import android.preference.PreferenceManager; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.telephony.AnomalyReporter; @@ -87,8 +87,6 @@ public class PhoneFactory { private static @Nullable EuiccCardController sEuiccCardController; private static SubscriptionManagerService sSubscriptionManagerService; - static private SubscriptionInfoUpdater sSubInfoRecordUpdater = null; - @UnsupportedAppUsage static private boolean sMadeDefaults = false; @UnsupportedAppUsage @@ -105,8 +103,6 @@ public class PhoneFactory { private static MetricsCollector sMetricsCollector; private static RadioInterfaceCapabilityController sRadioHalCapabilities; - private static boolean sSubscriptionManagerServiceEnabled = false; - //***** Class Methods public static void makeDefaultPhones(Context context) { @@ -123,12 +119,6 @@ public class PhoneFactory { if (!sMadeDefaults) { sContext = context; - // This is a temp flag which will be removed before U AOSP public release. - sSubscriptionManagerServiceEnabled = context.getResources().getBoolean( - com.android.internal.R.bool.config_using_subscription_manager_service) - || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY, - "enable_subscription_manager_service", false); - // create the telephony device controller. TelephonyDevController.create(); @@ -191,7 +181,7 @@ public class PhoneFactory { if (numPhones > 0) { final RadioConfig radioConfig = RadioConfig.make(context, - sCommandsInterfaces[0].getHalVersion()); + sCommandsInterfaces[0].getHalVersion(HAL_SERVICE_RADIO)); sRadioHalCapabilities = RadioInterfaceCapabilityController.init(radioConfig, sCommandsInterfaces[0]); } else { @@ -206,24 +196,12 @@ public class PhoneFactory { // call getInstance() sUiccController = UiccController.make(context); - - if (isSubscriptionManagerServiceEnabled()) { - Rlog.i(LOG_TAG, "Creating SubscriptionManagerService"); - sSubscriptionManagerService = new SubscriptionManagerService(context, - Looper.myLooper()); - } else { - Rlog.i(LOG_TAG, "Creating SubscriptionController"); - TelephonyComponentFactory.getInstance().inject(SubscriptionController.class - .getName()).initSubscriptionController(context); - } - - SubscriptionController sc = null; - if (!isSubscriptionManagerServiceEnabled()) { - sc = SubscriptionController.getInstance(); - } + Rlog.i(LOG_TAG, "Creating SubscriptionManagerService"); + sSubscriptionManagerService = new SubscriptionManagerService(context, + Looper.myLooper()); TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class. - getName()).initMultiSimSettingController(context, sc); + getName()).initMultiSimSettingController(context); if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY_EUICC)) { @@ -255,16 +233,6 @@ public class PhoneFactory { sMadeDefaults = true; - if (!isSubscriptionManagerServiceEnabled()) { - Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater "); - HandlerThread pfhandlerThread = new HandlerThread("PhoneFactoryHandlerThread"); - pfhandlerThread.start(); - sSubInfoRecordUpdater = TelephonyComponentFactory.getInstance().inject( - SubscriptionInfoUpdater.class.getName()) - .makeSubscriptionInfoUpdater(pfhandlerThread.getLooper(), context, - SubscriptionController.getInstance()); - } - // Only bring up IMS if the device supports having an IMS stack. if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY_IMS)) { @@ -303,14 +271,6 @@ public class PhoneFactory { } } - /** - * @return {@code true} if the new {@link SubscriptionManagerService} is enabled, otherwise the - * old {@link SubscriptionController} is used. - */ - public static boolean isSubscriptionManagerServiceEnabled() { - return sSubscriptionManagerServiceEnabled; - } - /** * Upon single SIM to dual SIM switch or vice versa, we dynamically allocate or de-allocate * Phone and CommandInterface objects. @@ -410,10 +370,6 @@ public class PhoneFactory { } } - public static SubscriptionInfoUpdater getSubscriptionInfoUpdater() { - return sSubInfoRecordUpdater; - } - /** * Get the network factory associated with a given phone ID. * @param phoneId the phone id @@ -450,7 +406,6 @@ public class PhoneFactory { * @param phoneId The phone's id. * @return the preferred network mode bitmask that should be set. */ - // TODO: Fix when we "properly" have TelephonyDevController/SubscriptionController .. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static int calculatePreferredNetworkType(int phoneId) { if (getPhone(phoneId) == null) { @@ -467,10 +422,7 @@ public class PhoneFactory { /* Gets the default subscription */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static int getDefaultSubscription() { - if (isSubscriptionManagerServiceEnabled()) { - return SubscriptionManagerService.getInstance().getDefaultSubId(); - } - return SubscriptionController.getInstance().getDefaultSubId(); + return SubscriptionManagerService.getInstance().getDefaultSubId(); } /* Returns User SMS Prompt property, enabled or not */ @@ -498,19 +450,7 @@ public class PhoneFactory { } /** - * Request a refresh of the embedded subscription list. - * - * @param cardId the card ID of the eUICC. - * @param callback Optional callback to execute after the refresh completes. Must terminate - * quickly as it will be called from SubscriptionInfoUpdater's handler thread. - */ - public static void requestEmbeddedSubscriptionInfoListRefresh( - int cardId, @Nullable Runnable callback) { - sSubInfoRecordUpdater.requestEmbeddedSubscriptionInfoListRefresh(cardId, callback); - } - - /** - * Get a the SmsController. + * Get the instance of {@link SmsController}. */ public static SmsController getSmsController() { synchronized (sLockProxyPhones) { @@ -613,25 +553,16 @@ public class PhoneFactory { pw.decreaseIndent(); pw.println("++++++++++++++++++++++++++++++++"); - if (!isSubscriptionManagerServiceEnabled()) { - pw.println("SubscriptionController:"); - pw.increaseIndent(); - try { - SubscriptionController.getInstance().dump(fd, pw, args); - } catch (Exception e) { - e.printStackTrace(); - } - pw.flush(); - pw.decreaseIndent(); - pw.println("++++++++++++++++++++++++++++++++"); + pw.flush(); + pw.decreaseIndent(); + pw.println("++++++++++++++++++++++++++++++++"); - pw.println("SubInfoRecordUpdater:"); - pw.increaseIndent(); - try { - sSubInfoRecordUpdater.dump(fd, pw, args); - } catch (Exception e) { - e.printStackTrace(); - } + pw.println("sRadioHalCapabilities:"); + pw.increaseIndent(); + try { + sRadioHalCapabilities.dump(fd, pw, args); + } catch (Exception e) { + e.printStackTrace(); } pw.flush(); pw.decreaseIndent(); diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java index 5b4d5e513f60bf597b91880e19a952b4e153470b..32c0c73f9b754a4805ef81488e418e396525d235 100644 --- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java +++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java @@ -16,7 +16,6 @@ package com.android.internal.telephony; -import android.annotation.IntDef; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -36,9 +35,9 @@ import android.telephony.emergency.EmergencyNumber; import com.android.internal.telephony.PhoneConstants.DataState; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Consumer; /** @@ -211,16 +210,6 @@ public interface PhoneInternalInterface { static final String REASON_DATA_UNTHROTTLED = "dataUnthrottled"; static final String REASON_TRAFFIC_DESCRIPTORS_UPDATED = "trafficDescriptorsUpdated"; - // Reasons for Radio being powered off - int RADIO_POWER_REASON_USER = 0; - int RADIO_POWER_REASON_THERMAL = 1; - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"RADIO_POWER_REASON_"}, - value = { - RADIO_POWER_REASON_USER, - RADIO_POWER_REASON_THERMAL}) - public @interface RadioPowerReason {} - // Used for band mode selection methods static final int BM_UNSPECIFIED = RILConstants.BAND_MODE_UNSPECIFIED; // automatic static final int BM_EURO_BAND = RILConstants.BAND_MODE_EURO; @@ -593,8 +582,9 @@ public interface PhoneInternalInterface { * getServiceState().getState() will not change immediately after this call. * registerForServiceStateChanged() to find out when the * request is complete. This will set the reason for radio power state as {@link - * #RADIO_POWER_REASON_USER}. This will not guarantee that the requested radio power state will - * actually be set. See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)} + * android.telephony.TelephonyManager#RADIO_POWER_REASON_USER}. This will not guarantee that the + * requested radio power state will actually be set. + * See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)} * for details. * * @param power true means "on", false means "off". @@ -620,8 +610,9 @@ public interface PhoneInternalInterface { * getServiceState().getState() will not change immediately after this call. * registerForServiceStateChanged() to find out when the * request is complete. This will set the reason for radio power state as {@link - * #RADIO_POWER_REASON_USER}. This will not guarantee that the requested radio power state will - * actually be set. See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)} + * android.telephony.TelephonyManager#RADIO_POWER_REASON_USER}. This will not guarantee that the + * requested radio power state will actually be set. + * See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)} * for details. * * @param power true means "on", false means "off". @@ -638,7 +629,7 @@ public interface PhoneInternalInterface { default void setRadioPower(boolean power, boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, boolean forceApply) { setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply, - RADIO_POWER_REASON_USER); + TelephonyManager.RADIO_POWER_REASON_USER); } /** @@ -656,10 +647,18 @@ public interface PhoneInternalInterface { * @param power true means "on", false means "off". * @param reason RadioPowerReason constant defining the reason why the radio power was set. */ - default void setRadioPowerForReason(boolean power, @RadioPowerReason int reason) { + default void setRadioPowerForReason(boolean power, + @TelephonyManager.RadioPowerReason int reason) { setRadioPowerForReason(power, false, false, false, reason); } + /** + * @return reasons for powering off radio. + */ + default Set getRadioPowerOffReasons() { + return new HashSet<>(); + } + /** * Sets the radio power on/off state with option to specify whether it's for emergency call * (off is sometimes called "airplane mode") and option to set the reason for setting the power @@ -686,7 +685,7 @@ public interface PhoneInternalInterface { */ default void setRadioPowerForReason(boolean power, boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, boolean forceApply, - @RadioPowerReason int reason) {} + @TelephonyManager.RadioPowerReason int reason) {} /** * Get the line 1 phone number (MSISDN). For CDMA phones, the MDN is returned @@ -1035,6 +1034,10 @@ public interface PhoneInternalInterface { */ String getImei(); + /** + * Retrieves IMEI type for phones. + */ + int getImeiType(); /** * Retrieves the IccPhoneBookInterfaceManager of the Phone */ diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java index 701a157f988aa9aadac45c8c8c33a841cd997453..20d6702f9da71fce974741b8493c383f1187adc9 100644 --- a/src/java/com/android/internal/telephony/PhoneNotifier.java +++ b/src/java/com/android/internal/telephony/PhoneNotifier.java @@ -18,6 +18,7 @@ package com.android.internal.telephony; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; +import android.telephony.Annotation; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SrvccState; import android.telephony.BarringInfo; @@ -31,8 +32,11 @@ import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager.DataEnabledReason; +import android.telephony.TelephonyManager.EmergencyCallbackModeStopReason; +import android.telephony.TelephonyManager.EmergencyCallbackModeType; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; import java.util.List; @@ -78,7 +82,10 @@ public interface PhoneNotifier { void notifyCellInfo(Phone sender, List cellInfo); - void notifyPreciseCallState(Phone sender); + /** Send a notification that precise call state changed. */ + void notifyPreciseCallState(Phone sender, String[] imsCallIds, + @Annotation.ImsCallServiceType int[] imsCallServiceTypes, + @Annotation.ImsCallType int[] imsCallTypes); void notifyDisconnectCause(Phone sender, int cause, int preciseCause); @@ -113,6 +120,9 @@ public interface PhoneNotifier { /** Notify of a change to the call quality of an active foreground call. */ void notifyCallQualityChanged(Phone sender, CallQuality callQuality, int callNetworkType); + /** Notify of a change to the media quality status of an active foreground call. */ + void notifyMediaQualityStatusChanged(Phone sender, MediaQualityStatus status); + /** Notify registration failed */ void notifyRegistrationFailed(Phone sender, @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode); @@ -132,4 +142,11 @@ public interface PhoneNotifier { /** Notify link capacity estimate has changed. */ void notifyLinkCapacityEstimateChanged(Phone sender, List linkCapacityEstimateList); + + /** Notify callback mode started. */ + void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type); + + /** Notify callback mode stopped. */ + void notifyCallbackModeStopped(Phone sender, @EmergencyCallbackModeType int type, + @EmergencyCallbackModeStopReason int reason); } diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java index 8048eba257d938f6132e62c4b2da795ff7d25f1a..d30a73c705da05ea0c2ff602fab3b7181e2d1240 100644 --- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java +++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java @@ -27,6 +27,7 @@ import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.RemoteException; @@ -41,10 +42,14 @@ import android.util.EventLog; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.IsimRecords; +import com.android.internal.telephony.uicc.SIMRecords; import com.android.internal.telephony.uicc.UiccCardApplication; import com.android.internal.telephony.uicc.UiccPort; import com.android.telephony.Rlog; +import java.util.ArrayList; +import java.util.List; + public class PhoneSubInfoController extends IPhoneSubInfo.Stub { private static final String TAG = "PhoneSubInfoController"; private static final boolean DBG = true; @@ -154,13 +159,8 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { long identity = Binder.clearCallingIdentity(); boolean isActive; try { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId, - callingPackage, callingFeatureId); - } else { - isActive = SubscriptionController.getInstance().isActiveSubId(subId, callingPackage, - callingFeatureId); - } + isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId, + callingPackage, callingFeatureId); } finally { Binder.restoreCallingIdentity(identity); } @@ -174,15 +174,12 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { } identity = Binder.clearCallingIdentity(); try { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(subId); - if (subInfo != null && !TextUtils.isEmpty(subInfo.getImsi())) { - return subInfo.getImsi(); - } - return null; + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(subId); + if (subInfo != null && !TextUtils.isEmpty(subInfo.getImsi())) { + return subInfo.getImsi(); } - return SubscriptionController.getInstance().getImsiPrivileged(subId); + return null; } finally { Binder.restoreCallingIdentity(identity); } @@ -350,6 +347,34 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { }); } + /** + * Fetches the IMS private user identity (EF_IMPI) based on subscriptionId. + * + * @param subId subscriptionId + * @return IMPI (IMS private user identity) of type string. + * @throws IllegalArgumentException if the subscriptionId is not valid + * @throws IllegalStateException in case the ISIM hasn’t been loaded. + * @throws SecurityException if the caller does not have the required permission + */ + public String getImsPrivateUserIdentity(int subId, String callingPackage, + String callingFeatureId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + throw new IllegalArgumentException("Invalid SubscriptionID = " + subId); + } + if (!TelephonyPermissions.checkCallingOrSelfUseIccAuthWithDeviceIdentifier(mContext, + callingPackage, callingFeatureId, "getImsPrivateUserIdentity")) { + throw (new SecurityException("No permissions to the caller")); + } + Phone phone = getPhone(subId); + assert phone != null; + IsimRecords isim = phone.getIsimRecords(); + if (isim != null) { + return isim.getIsimImpi(); + } else { + throw new IllegalStateException("ISIM is not loaded"); + } + } + /** * get the Isim Domain based on subId */ @@ -380,6 +405,42 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { }); } + /** + * Fetches the ISIM public user identities (EF_IMPU) from UICC based on subId + * + * @param subId subscriptionId + * @param callingPackage package name of the caller + * @param callingFeatureId feature Id of the caller + * @return List of public user identities of type android.net.Uri or empty list if + * EF_IMPU is not available. + * @throws IllegalArgumentException if the subscriptionId is not valid + * @throws IllegalStateException in case the ISIM hasn’t been loaded. + * @throws SecurityException if the caller does not have the required permission + */ + public List getImsPublicUserIdentities(int subId, String callingPackage, + String callingFeatureId) { + if (TelephonyPermissions. + checkCallingOrSelfReadPrivilegedPhoneStatePermissionOrReadPhoneNumber( + mContext, subId, callingPackage, callingFeatureId, "getImsPublicUserIdentities")) { + Phone phone = getPhone(subId); + assert phone != null; + IsimRecords isimRecords = phone.getIsimRecords(); + if (isimRecords != null) { + String[] impus = isimRecords.getIsimImpu(); + List impuList = new ArrayList<>(); + for (String impu : impus) { + if (impu != null && impu.trim().length() > 0) { + impuList.add(Uri.parse(impu)); + } + } + return impuList; + } + throw new IllegalStateException("ISIM is not loaded"); + } else { + throw new IllegalArgumentException("Invalid SubscriptionID = " + subId); + } + } + /** * get the Isim Ist based on subId */ @@ -410,6 +471,29 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { }); } + /** + * Returns the USIM service table that fetched from EFUST elementary field that are loaded + * based on the appType. + */ + public String getSimServiceTable(int subId, int appType) throws RemoteException { + return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getSimServiceTable", + (phone) -> { + UiccPort uiccPort = phone.getUiccPort(); + if (uiccPort == null || uiccPort.getUiccProfile() == null) { + loge("getSimServiceTable(): uiccPort or uiccProfile is null"); + return null; + } + UiccCardApplication uiccApp = uiccPort.getUiccProfile().getApplicationByType( + appType); + if (uiccApp == null) { + loge("getSimServiceTable(): no app with specified apptype=" + + appType); + return null; + } + return ((SIMRecords)uiccApp.getIccRecords()).getSimServiceTable(); + }); + } + @Override public String getIccSimChallengeResponse(int subId, int appType, int authType, String data, String callingPackage, String callingFeatureId) throws RemoteException { @@ -430,7 +514,9 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { } if (authType != UiccCardApplication.AUTH_CONTEXT_EAP_SIM - && authType != UiccCardApplication.AUTH_CONTEXT_EAP_AKA) { + && authType != UiccCardApplication.AUTH_CONTEXT_EAP_AKA + && authType != UiccCardApplication.AUTH_CONTEXT_GBA_BOOTSTRAP + && authType != UiccCardApplication.AUTHTYPE_GBA_NAF_KEY_EXTERNAL) { loge("getIccSimChallengeResponse() unsupported authType: " + authType); return null; } @@ -483,7 +569,7 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { if (phone != null) { return callMethodHelper.callMethod(phone); } else { - loge(message + " phone is null for Subscription:" + subId); + if (VDBG) loge(message + " phone is null for Subscription:" + subId); return null; } } finally { @@ -573,6 +659,37 @@ public class PhoneSubInfoController extends IPhoneSubInfo.Stub { } } + /** + * Returns SIP URI or tel URI of the Public Service Identity of the SM-SC fetched from + * EF_PSISMSC elementary field as defined in Section 4.5.9 (3GPP TS 31.102). + * @throws IllegalStateException in case if phone or UiccApplication is not available. + */ + public Uri getSmscIdentity(int subId, int appType) throws RemoteException { + Uri smscIdentityUri = callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getSmscIdentity", + (phone) -> { + try { + String smscIdentity = null; + UiccPort uiccPort = phone.getUiccPort(); + UiccCardApplication uiccApp = + uiccPort.getUiccProfile().getApplicationByType( + appType); + smscIdentity = (uiccApp != null) ? uiccApp.getIccRecords().getSmscIdentity() + : null; + if (TextUtils.isEmpty(smscIdentity)) { + return Uri.EMPTY; + } + return Uri.parse(smscIdentity); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getSmscIdentity(): Exception = " + ex); + return null; + } + }); + if (smscIdentityUri == null) { + throw new IllegalStateException("Telephony service error"); + } + return smscIdentityUri; + } + private void log(String s) { Rlog.d(TAG, s); } diff --git a/src/java/com/android/internal/telephony/ProxyController.java b/src/java/com/android/internal/telephony/ProxyController.java index 498953c6e0ae0599abf7663effd3b1720b866124..ed9982e5653c783b53ae38bd2f33d6e618bf3bf3 100644 --- a/src/java/com/android/internal/telephony/ProxyController.java +++ b/src/java/com/android/internal/telephony/ProxyController.java @@ -47,8 +47,10 @@ public class ProxyController { @VisibleForTesting static final int EVENT_START_RC_RESPONSE = 2; private static final int EVENT_APPLY_RC_RESPONSE = 3; - private static final int EVENT_FINISH_RC_RESPONSE = 4; - private static final int EVENT_TIMEOUT = 5; + @VisibleForTesting + public static final int EVENT_FINISH_RC_RESPONSE = 4; + @VisibleForTesting + public static final int EVENT_TIMEOUT = 5; @VisibleForTesting public static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 6; @@ -212,6 +214,7 @@ public class ProxyController { clearTransaction(); // Keep a wake lock until we finish radio capability changed + logd("Acquiring wake lock for setting radio capability"); mWakeLock.acquire(); return doSetRadioCapabilities(rafs); @@ -357,19 +360,25 @@ public class ProxyController { } } RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result; - if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) { + // Added exception condition to continue to mark as transaction fail case. + // Checking session validity during exception is not valid + if (ar.exception == null + && ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId))) { logd("onStartRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId + " rc=" + rc); return; } mRadioAccessFamilyStatusCounter--; - int id = rc.getPhoneId(); + //rc.getPhoneId() moved to avoid Null Pointer Exception, since when exception occurs + //its expected rc is null. if (ar.exception != null) { - logd("onStartRadioCapabilityResponse: Error response session=" + rc.getSession()); - logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=FAIL"); - mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL; + logd("onStartRadioCapabilityResponse got exception=" + ar.exception); + //mSetRadioAccessFamilyStatus will be set anyway to SET_RC_STATUS_FAIL + // if either of them fail at issueFinish() method below,i.e. both phone id count + // is set to SET_RC_STATUS_FAIL. mTransactionFailed = true; } else { + int id = rc.getPhoneId(); logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=STARTED"); mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_STARTED; } @@ -481,13 +490,20 @@ public class ProxyController { * @param msg obj field isa RadioCapability */ void onFinishRadioCapabilityResponse(Message msg) { - RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result; - if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) { - logd("onFinishRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId - + " rc=" + rc); - return; - } synchronized (mSetRadioAccessFamilyStatus) { + AsyncResult ar = (AsyncResult) msg.obj; + RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result; + // Added exception condition on finish to continue to revert if exception occurred. + // Checking session validity during exception is not valid + if (ar.exception == null + && ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId))) { + logd("onFinishRadioCapabilityResponse: Ignore session=" + + mRadioCapabilitySessionId + " rc=" + rc); + return; + } + if (ar.exception != null) { + logd("onFinishRadioCapabilityResponse got exception=" + ar.exception); + } logd(" onFinishRadioCapabilityResponse mRadioAccessFamilyStatusCounter=" + mRadioAccessFamilyStatusCounter); mRadioAccessFamilyStatusCounter--; @@ -575,7 +591,6 @@ public class ProxyController { clearTransaction(); } else { intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_FAILED); - // now revert. mTransactionFailed = false; RadioAccessFamily[] rafs = new RadioAccessFamily[mPhones.length]; @@ -602,6 +617,7 @@ public class ProxyController { } if (isWakeLockHeld()) { + logd("clearTransaction:checking wakelock held and releasing"); mWakeLock.release(); } } diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java index 0249f47abf573674d056a26b3717ab37bc3dbd0d..5ecdfcbbd481d28a5a63d513bd5dcbd8076ece6c 100644 --- a/src/java/com/android/internal/telephony/RIL.java +++ b/src/java/com/android/internal/telephony/RIL.java @@ -16,6 +16,15 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_DATA; +import static android.telephony.TelephonyManager.HAL_SERVICE_IMS; +import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING; +import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; +import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO; +import static android.telephony.TelephonyManager.HAL_SERVICE_SIM; +import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE; + import static com.android.internal.telephony.RILConstants.*; import android.annotation.NonNull; @@ -45,7 +54,9 @@ import android.os.Trace; import android.os.WorkSource; import android.provider.Settings; import android.sysprop.TelephonyProperties; +import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants.AccessNetworkType; +import android.telephony.BarringInfo; import android.telephony.CarrierRestrictionRules; import android.telephony.CellInfo; import android.telephony.CellSignalStrengthCdma; @@ -55,6 +66,7 @@ import android.telephony.CellSignalStrengthNr; import android.telephony.CellSignalStrengthTdscdma; import android.telephony.CellSignalStrengthWcdma; import android.telephony.ClientRequestStats; +import android.telephony.DomainSelectionService; import android.telephony.ImsiEncryptionInfo; import android.telephony.ModemActivityInfo; import android.telephony.NeighboringCellInfo; @@ -67,18 +79,24 @@ import android.telephony.SignalThresholdInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyHistogram; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.HalService; import android.telephony.TelephonyManager.PrefNetworkMode; import android.telephony.data.DataProfile; import android.telephony.data.NetworkSliceInfo; import android.telephony.data.TrafficDescriptor; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.RegistrationManager; +import android.telephony.ims.feature.ConnectionFailureInfo; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.text.TextUtils; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.cdma.CdmaInformationRecords; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; +import com.android.internal.telephony.emergency.EmergencyConstants; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; +import com.android.internal.telephony.imsphone.ImsCallInfo; import com.android.internal.telephony.metrics.ModemRestartStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.nano.TelephonyProto.SmsSession; @@ -92,8 +110,10 @@ import java.io.FileDescriptor; 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.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; @@ -132,6 +152,9 @@ public class RIL extends BaseCommands implements CommandsInterface { public static final int FOR_ACK_WAKELOCK = 1; private final ClientWakelockTracker mClientWakelockTracker = new ClientWakelockTracker(); + /** @hide */ + public static final HalVersion RADIO_HAL_VERSION_UNSUPPORTED = HalVersion.UNSUPPORTED; + /** @hide */ public static final HalVersion RADIO_HAL_VERSION_UNKNOWN = HalVersion.UNKNOWN; @@ -159,8 +182,11 @@ public class RIL extends BaseCommands implements CommandsInterface { /** @hide */ public static final HalVersion RADIO_HAL_VERSION_2_0 = new HalVersion(2, 0); - // IRadio version - private HalVersion mRadioVersion = RADIO_HAL_VERSION_UNKNOWN; + /** @hide */ + public static final HalVersion RADIO_HAL_VERSION_2_1 = new HalVersion(2, 1); + + // Hal version + private Map mHalVersion = new HashMap<>(); //***** Instance Variables @@ -198,15 +224,9 @@ public class RIL extends BaseCommands implements CommandsInterface { private static final String PROPERTY_IS_VONR_ENABLED = "persist.radio.is_vonr_enabled_"; - static final int RADIO_SERVICE = 0; - static final int DATA_SERVICE = 1; - static final int MESSAGING_SERVICE = 2; - static final int MODEM_SERVICE = 3; - static final int NETWORK_SERVICE = 4; - static final int SIM_SERVICE = 5; - static final int VOICE_SERVICE = 6; - static final int MIN_SERVICE_IDX = RADIO_SERVICE; - static final int MAX_SERVICE_IDX = VOICE_SERVICE; + public static final int MIN_SERVICE_IDX = HAL_SERVICE_RADIO; + + public static final int MAX_SERVICE_IDX = HAL_SERVICE_IMS; /** * An array of sets that records if services are disabled in the HAL for a specific phone ID @@ -233,6 +253,8 @@ public class RIL extends BaseCommands implements CommandsInterface { private volatile IRadio mRadioProxy = null; private DataResponse mDataResponse; private DataIndication mDataIndication; + private ImsResponse mImsResponse; + private ImsIndication mImsIndication; private MessagingResponse mMessagingResponse; private MessagingIndication mMessagingIndication; private ModemResponse mModemResponse; @@ -362,11 +384,11 @@ public class RIL extends BaseCommands implements CommandsInterface { case EVENT_AIDL_PROXY_DEAD: int aidlService = msg.arg1; - AtomicLong obj = (AtomicLong) msg.obj; - riljLog("handleMessage: EVENT_AIDL_PROXY_DEAD cookie = " + msg.obj + long msgCookie = (long) msg.obj; + riljLog("handleMessage: EVENT_AIDL_PROXY_DEAD cookie = " + msgCookie + ", service = " + serviceToString(aidlService) + ", cookie = " + mServiceCookies.get(aidlService)); - if (obj.get() == mServiceCookies.get(aidlService).get()) { + if (msgCookie == mServiceCookies.get(aidlService).get()) { mIsRadioProxyInitialized = false; resetProxyAndRequestList(aidlService); } @@ -413,8 +435,8 @@ public class RIL extends BaseCommands implements CommandsInterface { public void serviceDied(long cookie) { // Deal with service going away riljLog("serviceDied"); - mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD, RADIO_SERVICE, - 0 /* ignored arg2 */, cookie)); + mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD, + HAL_SERVICE_RADIO, 0 /* ignored arg2 */, cookie)); } } @@ -428,8 +450,11 @@ public class RIL extends BaseCommands implements CommandsInterface { public void linkToDeath(IBinder service) throws RemoteException { if (service != null) { + riljLog("Linked to death for service " + serviceToString(mService)); mBinder = service; mBinder.linkToDeath(this, (int) mServiceCookies.get(mService).incrementAndGet()); + } else { + riljLoge("Unable to link to death for service " + serviceToString(mService)); } } @@ -444,13 +469,13 @@ public class RIL extends BaseCommands implements CommandsInterface { public void binderDied() { riljLog("Service " + serviceToString(mService) + " has died."); mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_AIDL_PROXY_DEAD, mService, - 0 /* ignored arg2 */, mServiceCookies.get(mService))); + 0 /* ignored arg2 */, mServiceCookies.get(mService).get())); unlinkToDeath(); } } private synchronized void resetProxyAndRequestList(int service) { - if (service == RADIO_SERVICE) { + if (service == HAL_SERVICE_RADIO) { mRadioProxy = null; } else { mServiceProxies.get(service).clear(); @@ -467,7 +492,7 @@ public class RIL extends BaseCommands implements CommandsInterface { // Clear request list on close clearRequestList(RADIO_NOT_AVAILABLE, false); - if (service == RADIO_SERVICE) { + if (service == HAL_SERVICE_RADIO) { getRadioProxy(null); } else { getRadioServiceProxy(service, null); @@ -496,13 +521,13 @@ public class RIL extends BaseCommands implements CommandsInterface { // Disable HIDL service if (mRadioProxy != null) { riljLog("Disable HIDL service"); - mDisabledRadioServices.get(RADIO_SERVICE).add(mPhoneId); + mDisabledRadioServices.get(HAL_SERVICE_RADIO).add(mPhoneId); } mMockModem.bindAllMockModemService(); for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) { - if (service == RADIO_SERVICE) continue; + if (service == HAL_SERVICE_RADIO) continue; int retryCount = 0; IBinder binder; @@ -537,14 +562,26 @@ public class RIL extends BaseCommands implements CommandsInterface { if ((serviceName == null) || (!serviceBound)) { if (serviceBound) riljLog("Unbinding to MockModemService"); - if (mDisabledRadioServices.get(RADIO_SERVICE).contains(mPhoneId)) { - mDisabledRadioServices.get(RADIO_SERVICE).clear(); + if (mDisabledRadioServices.get(HAL_SERVICE_RADIO).contains(mPhoneId)) { + mDisabledRadioServices.get(HAL_SERVICE_RADIO).clear(); } if (mMockModem != null) { - mRadioVersion = RADIO_HAL_VERSION_UNKNOWN; mMockModem = null; for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) { + if (service == HAL_SERVICE_RADIO) { + if (isRadioVersion2_0()) { + mHalVersion.put(service, RADIO_HAL_VERSION_2_0); + } else { + mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN); + } + } else { + if (isRadioServiceSupported(service)) { + mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN); + } else { + mHalVersion.put(service, RADIO_HAL_VERSION_UNSUPPORTED); + } + } resetProxyAndRequestList(service); } } @@ -586,7 +623,10 @@ public class RIL extends BaseCommands implements CommandsInterface { /** Returns a {@link IRadio} instance or null if the service is not available. */ @VisibleForTesting public synchronized IRadio getRadioProxy(Message result) { - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) return null; + if (mHalVersion.containsKey(HAL_SERVICE_RADIO) + && mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + return null; + } if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return null; if (!mIsCellularSupported) { if (RILJ_LOGV) riljLog("getRadioProxy: Not calling getService(): wifi-only"); @@ -603,14 +643,14 @@ public class RIL extends BaseCommands implements CommandsInterface { } try { - if (mDisabledRadioServices.get(RADIO_SERVICE).contains(mPhoneId)) { + if (mDisabledRadioServices.get(HAL_SERVICE_RADIO).contains(mPhoneId)) { riljLoge("getRadioProxy: mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId] + " is disabled"); } else { try { mRadioProxy = android.hardware.radio.V1_6.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true); - mRadioVersion = RADIO_HAL_VERSION_1_6; + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_6); } catch (NoSuchElementException e) { } @@ -618,7 +658,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { mRadioProxy = android.hardware.radio.V1_5.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true); - mRadioVersion = RADIO_HAL_VERSION_1_5; + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_5); } catch (NoSuchElementException e) { } } @@ -627,7 +667,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { mRadioProxy = android.hardware.radio.V1_4.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true); - mRadioVersion = RADIO_HAL_VERSION_1_4; + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_4); } catch (NoSuchElementException e) { } } @@ -636,7 +676,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { mRadioProxy = android.hardware.radio.V1_3.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true); - mRadioVersion = RADIO_HAL_VERSION_1_3; + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_3); } catch (NoSuchElementException e) { } } @@ -645,7 +685,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { mRadioProxy = android.hardware.radio.V1_2.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true); - mRadioVersion = RADIO_HAL_VERSION_1_2; + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_2); } catch (NoSuchElementException e) { } } @@ -654,7 +694,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { mRadioProxy = android.hardware.radio.V1_1.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true); - mRadioVersion = RADIO_HAL_VERSION_1_1; + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_1); } catch (NoSuchElementException e) { } } @@ -663,7 +703,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { mRadioProxy = android.hardware.radio.V1_0.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true); - mRadioVersion = RADIO_HAL_VERSION_1_0; + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_0); } catch (NoSuchElementException e) { } } @@ -672,11 +712,11 @@ public class RIL extends BaseCommands implements CommandsInterface { if (!mIsRadioProxyInitialized) { mIsRadioProxyInitialized = true; mRadioProxy.linkToDeath(mRadioProxyDeathRecipient, - mServiceCookies.get(RADIO_SERVICE).incrementAndGet()); + mServiceCookies.get(HAL_SERVICE_RADIO).incrementAndGet()); mRadioProxy.setResponseFunctions(mRadioResponse, mRadioIndication); } } else { - mDisabledRadioServices.get(RADIO_SERVICE).add(mPhoneId); + mDisabledRadioServices.get(HAL_SERVICE_RADIO).add(mPhoneId); riljLoge("getRadioProxy: set mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId] + " as disabled"); } @@ -701,29 +741,32 @@ public class RIL extends BaseCommands implements CommandsInterface { /** * Returns a {@link RadioDataProxy}, {@link RadioMessagingProxy}, {@link RadioModemProxy}, - * {@link RadioNetworkProxy}, {@link RadioSimProxy}, {@link RadioVoiceProxy}, or an empty {@link RadioServiceProxy} - * if the service is not available. + * {@link RadioNetworkProxy}, {@link RadioSimProxy}, {@link RadioVoiceProxy}, + * {@link RadioImsProxy}, or null if the service is not available. */ @NonNull public T getRadioServiceProxy(Class serviceClass, Message result) { if (serviceClass == RadioDataProxy.class) { - return (T) getRadioServiceProxy(DATA_SERVICE, result); + return (T) getRadioServiceProxy(HAL_SERVICE_DATA, result); } if (serviceClass == RadioMessagingProxy.class) { - return (T) getRadioServiceProxy(MESSAGING_SERVICE, result); + return (T) getRadioServiceProxy(HAL_SERVICE_MESSAGING, result); } if (serviceClass == RadioModemProxy.class) { - return (T) getRadioServiceProxy(MODEM_SERVICE, result); + return (T) getRadioServiceProxy(HAL_SERVICE_MODEM, result); } if (serviceClass == RadioNetworkProxy.class) { - return (T) getRadioServiceProxy(NETWORK_SERVICE, result); + return (T) getRadioServiceProxy(HAL_SERVICE_NETWORK, result); } if (serviceClass == RadioSimProxy.class) { - return (T) getRadioServiceProxy(SIM_SERVICE, result); + return (T) getRadioServiceProxy(HAL_SERVICE_SIM, result); } if (serviceClass == RadioVoiceProxy.class) { - return (T) getRadioServiceProxy(VOICE_SERVICE, result); + return (T) getRadioServiceProxy(HAL_SERVICE_VOICE, result); + } + if (serviceClass == RadioImsProxy.class) { + return (T) getRadioServiceProxy(HAL_SERVICE_IMS, result); } riljLoge("getRadioServiceProxy: unrecognized " + serviceClass); return null; @@ -731,12 +774,15 @@ public class RIL extends BaseCommands implements CommandsInterface { /** * Returns a {@link RadioServiceProxy}, which is empty if the service is not available. - * For RADIO_SERVICE, use {@link #getRadioProxy} instead, as this will always return null. + * For HAL_SERVICE_RADIO, use {@link #getRadioProxy} instead, as this will always return null. */ @VisibleForTesting @NonNull public synchronized RadioServiceProxy getRadioServiceProxy(int service, Message result) { if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return mServiceProxies.get(service); + if ((service >= HAL_SERVICE_IMS) && !isRadioServiceSupported(service)) { + return mServiceProxies.get(service); + } if (!mIsCellularSupported) { if (RILJ_LOGV) riljLog("getRadioServiceProxy: Not calling getService(): wifi-only"); if (result != null) { @@ -753,168 +799,190 @@ public class RIL extends BaseCommands implements CommandsInterface { } try { - if (mDisabledRadioServices.get(service).contains(mPhoneId)) { + if (mMockModem == null && mDisabledRadioServices.get(service).contains(mPhoneId)) { riljLoge("getRadioServiceProxy: " + serviceToString(service) + " for " + HIDL_SERVICE_NAME[mPhoneId] + " is disabled"); } else { IBinder binder; switch (service) { - case DATA_SERVICE: + case HAL_SERVICE_DATA: if (mMockModem == null) { binder = ServiceManager.waitForDeclaredService( android.hardware.radio.data.IRadioData.DESCRIPTOR + "/" + HIDL_SERVICE_NAME[mPhoneId]); } else { - binder = mMockModem.getServiceBinder(DATA_SERVICE); + binder = mMockModem.getServiceBinder(HAL_SERVICE_DATA); } if (binder != null) { - mRadioVersion = RADIO_HAL_VERSION_2_0; - ((RadioDataProxy) serviceProxy).setAidl(mRadioVersion, + mHalVersion.put(service, ((RadioDataProxy) serviceProxy).setAidl( + mHalVersion.get(service), android.hardware.radio.data.IRadioData.Stub.asInterface( - binder)); + binder))); } break; - case MESSAGING_SERVICE: + case HAL_SERVICE_MESSAGING: if (mMockModem == null) { binder = ServiceManager.waitForDeclaredService( android.hardware.radio.messaging.IRadioMessaging.DESCRIPTOR + "/" + HIDL_SERVICE_NAME[mPhoneId]); } else { - binder = mMockModem.getServiceBinder(MESSAGING_SERVICE); + binder = mMockModem.getServiceBinder(HAL_SERVICE_MESSAGING); } if (binder != null) { - mRadioVersion = RADIO_HAL_VERSION_2_0; - ((RadioMessagingProxy) serviceProxy).setAidl(mRadioVersion, + mHalVersion.put(service, ((RadioMessagingProxy) serviceProxy).setAidl( + mHalVersion.get(service), android.hardware.radio.messaging.IRadioMessaging.Stub - .asInterface(binder)); + .asInterface(binder))); } break; - case MODEM_SERVICE: + case HAL_SERVICE_MODEM: if (mMockModem == null) { binder = ServiceManager.waitForDeclaredService( android.hardware.radio.modem.IRadioModem.DESCRIPTOR + "/" + HIDL_SERVICE_NAME[mPhoneId]); } else { - binder = mMockModem.getServiceBinder(MODEM_SERVICE); + binder = mMockModem.getServiceBinder(HAL_SERVICE_MODEM); } if (binder != null) { - mRadioVersion = RADIO_HAL_VERSION_2_0; - ((RadioModemProxy) serviceProxy).setAidl(mRadioVersion, + mHalVersion.put(service, ((RadioModemProxy) serviceProxy).setAidl( + mHalVersion.get(service), android.hardware.radio.modem.IRadioModem.Stub - .asInterface(binder)); + .asInterface(binder))); } break; - case NETWORK_SERVICE: + case HAL_SERVICE_NETWORK: if (mMockModem == null) { binder = ServiceManager.waitForDeclaredService( android.hardware.radio.network.IRadioNetwork.DESCRIPTOR + "/" + HIDL_SERVICE_NAME[mPhoneId]); } else { - binder = mMockModem.getServiceBinder(NETWORK_SERVICE); + binder = mMockModem.getServiceBinder(HAL_SERVICE_NETWORK); } if (binder != null) { - mRadioVersion = RADIO_HAL_VERSION_2_0; - ((RadioNetworkProxy) serviceProxy).setAidl(mRadioVersion, + mHalVersion.put(service, ((RadioNetworkProxy) serviceProxy).setAidl( + mHalVersion.get(service), android.hardware.radio.network.IRadioNetwork.Stub - .asInterface(binder)); + .asInterface(binder))); } break; - case SIM_SERVICE: + case HAL_SERVICE_SIM: if (mMockModem == null) { binder = ServiceManager.waitForDeclaredService( android.hardware.radio.sim.IRadioSim.DESCRIPTOR + "/" + HIDL_SERVICE_NAME[mPhoneId]); } else { - binder = mMockModem.getServiceBinder(SIM_SERVICE); + binder = mMockModem.getServiceBinder(HAL_SERVICE_SIM); } if (binder != null) { - mRadioVersion = RADIO_HAL_VERSION_2_0; - ((RadioSimProxy) serviceProxy).setAidl(mRadioVersion, + mHalVersion.put(service, ((RadioSimProxy) serviceProxy).setAidl( + mHalVersion.get(service), android.hardware.radio.sim.IRadioSim.Stub - .asInterface(binder)); + .asInterface(binder))); } break; - case VOICE_SERVICE: + case HAL_SERVICE_VOICE: if (mMockModem == null) { binder = ServiceManager.waitForDeclaredService( android.hardware.radio.voice.IRadioVoice.DESCRIPTOR + "/" + HIDL_SERVICE_NAME[mPhoneId]); } else { - binder = mMockModem.getServiceBinder(VOICE_SERVICE); + binder = mMockModem.getServiceBinder(HAL_SERVICE_VOICE); } if (binder != null) { - mRadioVersion = RADIO_HAL_VERSION_2_0; - ((RadioVoiceProxy) serviceProxy).setAidl(mRadioVersion, + mHalVersion.put(service, ((RadioVoiceProxy) serviceProxy).setAidl( + mHalVersion.get(service), android.hardware.radio.voice.IRadioVoice.Stub - .asInterface(binder)); + .asInterface(binder))); + } + break; + case HAL_SERVICE_IMS: + if (mMockModem == null) { + binder = ServiceManager.waitForDeclaredService( + android.hardware.radio.ims.IRadioIms.DESCRIPTOR + "/" + + HIDL_SERVICE_NAME[mPhoneId]); + } else { + binder = mMockModem.getServiceBinder(HAL_SERVICE_IMS); + } + if (binder != null) { + mHalVersion.put(service, ((RadioImsProxy) serviceProxy).setAidl( + mHalVersion.get(service), + android.hardware.radio.ims.IRadioIms.Stub + .asInterface(binder))); } break; } - if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) { + if (serviceProxy.isEmpty() + && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) { try { - mRadioVersion = RADIO_HAL_VERSION_1_6; - serviceProxy.setHidl(mRadioVersion, + mHalVersion.put(service, RADIO_HAL_VERSION_1_6); + serviceProxy.setHidl(mHalVersion.get(service), android.hardware.radio.V1_6.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true)); } catch (NoSuchElementException e) { } } - if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) { + if (serviceProxy.isEmpty() + && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) { try { - mRadioVersion = RADIO_HAL_VERSION_1_5; - serviceProxy.setHidl(mRadioVersion, + mHalVersion.put(service, RADIO_HAL_VERSION_1_5); + serviceProxy.setHidl(mHalVersion.get(service), android.hardware.radio.V1_5.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true)); } catch (NoSuchElementException e) { } } - if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) { + if (serviceProxy.isEmpty() + && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) { try { - mRadioVersion = RADIO_HAL_VERSION_1_4; - serviceProxy.setHidl(mRadioVersion, + mHalVersion.put(service, RADIO_HAL_VERSION_1_4); + serviceProxy.setHidl(mHalVersion.get(service), android.hardware.radio.V1_4.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true)); } catch (NoSuchElementException e) { } } - if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) { + if (serviceProxy.isEmpty() + && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) { try { - mRadioVersion = RADIO_HAL_VERSION_1_3; - serviceProxy.setHidl(mRadioVersion, + mHalVersion.put(service, RADIO_HAL_VERSION_1_3); + serviceProxy.setHidl(mHalVersion.get(service), android.hardware.radio.V1_3.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true)); } catch (NoSuchElementException e) { } } - if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) { + if (serviceProxy.isEmpty() + && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) { try { - mRadioVersion = RADIO_HAL_VERSION_1_2; - serviceProxy.setHidl(mRadioVersion, + mHalVersion.put(service, RADIO_HAL_VERSION_1_2); + serviceProxy.setHidl(mHalVersion.get(service), android.hardware.radio.V1_2.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true)); } catch (NoSuchElementException e) { } } - if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) { + if (serviceProxy.isEmpty() + && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) { try { - mRadioVersion = RADIO_HAL_VERSION_1_1; - serviceProxy.setHidl(mRadioVersion, + mHalVersion.put(service, RADIO_HAL_VERSION_1_1); + serviceProxy.setHidl(mHalVersion.get(service), android.hardware.radio.V1_1.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true)); } catch (NoSuchElementException e) { } } - if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) { + if (serviceProxy.isEmpty() + && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) { try { - mRadioVersion = RADIO_HAL_VERSION_1_0; - serviceProxy.setHidl(mRadioVersion, + mHalVersion.put(service, RADIO_HAL_VERSION_1_0); + serviceProxy.setHidl(mHalVersion.get(service), android.hardware.radio.V1_0.IRadio.getService( HIDL_SERVICE_NAME[mPhoneId], true)); } catch (NoSuchElementException e) { @@ -924,57 +992,65 @@ public class RIL extends BaseCommands implements CommandsInterface { if (!serviceProxy.isEmpty()) { if (serviceProxy.isAidl()) { switch (service) { - case DATA_SERVICE: + case HAL_SERVICE_DATA: mDeathRecipients.get(service).linkToDeath( ((RadioDataProxy) serviceProxy).getAidl().asBinder()); ((RadioDataProxy) serviceProxy).getAidl().setResponseFunctions( mDataResponse, mDataIndication); break; - case MESSAGING_SERVICE: + case HAL_SERVICE_MESSAGING: mDeathRecipients.get(service).linkToDeath( ((RadioMessagingProxy) serviceProxy).getAidl().asBinder()); ((RadioMessagingProxy) serviceProxy).getAidl().setResponseFunctions( mMessagingResponse, mMessagingIndication); break; - case MODEM_SERVICE: + case HAL_SERVICE_MODEM: mDeathRecipients.get(service).linkToDeath( ((RadioModemProxy) serviceProxy).getAidl().asBinder()); ((RadioModemProxy) serviceProxy).getAidl().setResponseFunctions( mModemResponse, mModemIndication); break; - case NETWORK_SERVICE: + case HAL_SERVICE_NETWORK: mDeathRecipients.get(service).linkToDeath( ((RadioNetworkProxy) serviceProxy).getAidl().asBinder()); ((RadioNetworkProxy) serviceProxy).getAidl().setResponseFunctions( mNetworkResponse, mNetworkIndication); break; - case SIM_SERVICE: + case HAL_SERVICE_SIM: mDeathRecipients.get(service).linkToDeath( ((RadioSimProxy) serviceProxy).getAidl().asBinder()); ((RadioSimProxy) serviceProxy).getAidl().setResponseFunctions( mSimResponse, mSimIndication); break; - case VOICE_SERVICE: + case HAL_SERVICE_VOICE: mDeathRecipients.get(service).linkToDeath( ((RadioVoiceProxy) serviceProxy).getAidl().asBinder()); ((RadioVoiceProxy) serviceProxy).getAidl().setResponseFunctions( mVoiceResponse, mVoiceIndication); break; + case HAL_SERVICE_IMS: + mDeathRecipients.get(service).linkToDeath( + ((RadioImsProxy) serviceProxy).getAidl().asBinder()); + ((RadioImsProxy) serviceProxy).getAidl().setResponseFunctions( + mImsResponse, mImsIndication); + break; } } else { - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + if (mHalVersion.get(service) + .greaterOrEqual(RADIO_HAL_VERSION_2_0)) { throw new AssertionError("serviceProxy shouldn't be HIDL with HAL 2.0"); } if (!mIsRadioProxyInitialized) { mIsRadioProxyInitialized = true; serviceProxy.getHidl().linkToDeath(mRadioProxyDeathRecipient, - mServiceCookies.get(service).incrementAndGet()); + mServiceCookies.get(HAL_SERVICE_RADIO).incrementAndGet()); serviceProxy.getHidl().setResponseFunctions( mRadioResponse, mRadioIndication); } } } else { mDisabledRadioServices.get(service).add(mPhoneId); + mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN); riljLoge("getRadioServiceProxy: set " + serviceToString(service) + " for " + HIDL_SERVICE_NAME[mPhoneId] + " as disabled"); } @@ -1003,7 +1079,7 @@ public class RIL extends BaseCommands implements CommandsInterface { for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) { if (active) { // Try to connect to RIL services and set response functions. - if (service == RADIO_SERVICE) { + if (service == HAL_SERVICE_RADIO) { getRadioProxy(null); } else { getRadioServiceProxy(service, null); @@ -1044,7 +1120,11 @@ public class RIL extends BaseCommands implements CommandsInterface { mRadioBugDetector = new RadioBugDetector(context, mPhoneId); } try { - if (isRadioVersion2_0()) mRadioVersion = RADIO_HAL_VERSION_2_0; + if (isRadioVersion2_0()) { + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_2_0); + } else { + mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_UNKNOWN); + } } catch (SecurityException ex) { /* TODO(b/211920208): instead of the following workaround (guessing if we're in a test * based on proxies being populated), mock ServiceManager to not throw @@ -1060,6 +1140,8 @@ public class RIL extends BaseCommands implements CommandsInterface { mRadioIndication = new RadioIndication(this); mDataResponse = new DataResponse(this); mDataIndication = new DataIndication(this); + mImsResponse = new ImsResponse(this); + mImsIndication = new ImsIndication(this); mMessagingResponse = new MessagingResponse(this); mMessagingIndication = new MessagingIndication(this); mModemResponse = new ModemResponse(this); @@ -1073,19 +1155,33 @@ public class RIL extends BaseCommands implements CommandsInterface { mRilHandler = new RilHandler(); mRadioProxyDeathRecipient = new RadioProxyDeathRecipient(); for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) { - if (service != RADIO_SERVICE) { + if (service != HAL_SERVICE_RADIO) { + try { + if (isRadioServiceSupported(service)) { + mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN); + } else { + mHalVersion.put(service, RADIO_HAL_VERSION_UNSUPPORTED); + } + } catch (SecurityException ex) { + /* TODO(b/211920208): instead of the following workaround (guessing if + * we're in a test based on proxies being populated), mock ServiceManager + * to not throw SecurityException and return correct value based on what + * HAL we're testing. */ + if (proxies == null) throw ex; + } mDeathRecipients.put(service, new BinderServiceDeathRecipient(service)); } mDisabledRadioServices.put(service, new HashSet<>()); mServiceCookies.put(service, new AtomicLong(0)); } if (proxies == null) { - mServiceProxies.put(DATA_SERVICE, new RadioDataProxy()); - mServiceProxies.put(MESSAGING_SERVICE, new RadioMessagingProxy()); - mServiceProxies.put(MODEM_SERVICE, new RadioModemProxy()); - mServiceProxies.put(NETWORK_SERVICE, new RadioNetworkProxy()); - mServiceProxies.put(SIM_SERVICE, new RadioSimProxy()); - mServiceProxies.put(VOICE_SERVICE, new RadioVoiceProxy()); + mServiceProxies.put(HAL_SERVICE_DATA, new RadioDataProxy()); + mServiceProxies.put(HAL_SERVICE_MESSAGING, new RadioMessagingProxy()); + mServiceProxies.put(HAL_SERVICE_MODEM, new RadioModemProxy()); + mServiceProxies.put(HAL_SERVICE_NETWORK, new RadioNetworkProxy()); + mServiceProxies.put(HAL_SERVICE_SIM, new RadioSimProxy()); + mServiceProxies.put(HAL_SERVICE_VOICE, new RadioVoiceProxy()); + mServiceProxies.put(HAL_SERVICE_IMS, new RadioImsProxy()); } else { mServiceProxies = proxies; } @@ -1114,7 +1210,7 @@ public class RIL extends BaseCommands implements CommandsInterface { // Set radio callback; needed to set RadioIndication callback (should be done after // wakelock stuff is initialized above as callbacks are received on separate binder threads) for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) { - if (service == RADIO_SERVICE) { + if (service == HAL_SERVICE_RADIO) { getRadioProxy(null); } else { if (proxies == null) { @@ -1122,30 +1218,62 @@ public class RIL extends BaseCommands implements CommandsInterface { getRadioServiceProxy(service, null); } } - } - if (RILJ_LOGD) { - riljLog("Radio HAL version: " + mRadioVersion); + if (RILJ_LOGD) { + riljLog("HAL version of " + serviceToString(service) + + ": " + mHalVersion.get(service)); + } } } private boolean isRadioVersion2_0() { - final String[] serviceNames = new String[] { - android.hardware.radio.data.IRadioData.DESCRIPTOR, - android.hardware.radio.messaging.IRadioMessaging.DESCRIPTOR, - android.hardware.radio.modem.IRadioModem.DESCRIPTOR, - android.hardware.radio.network.IRadioNetwork.DESCRIPTOR, - android.hardware.radio.sim.IRadioSim.DESCRIPTOR, - android.hardware.radio.voice.IRadioVoice.DESCRIPTOR, - }; - for (String serviceName : serviceNames) { - if (ServiceManager.isDeclared(serviceName + '/' + HIDL_SERVICE_NAME[mPhoneId])) { + for (int service = HAL_SERVICE_DATA; service <= MAX_SERVICE_IDX; service++) { + if (isRadioServiceSupported(service)) { return true; } } return false; } + private boolean isRadioServiceSupported(int service) { + String serviceName = ""; + + if (service == HAL_SERVICE_RADIO) { + return true; + } + + switch (service) { + case HAL_SERVICE_DATA: + serviceName = android.hardware.radio.data.IRadioData.DESCRIPTOR; + break; + case HAL_SERVICE_MESSAGING: + serviceName = android.hardware.radio.messaging.IRadioMessaging.DESCRIPTOR; + break; + case HAL_SERVICE_MODEM: + serviceName = android.hardware.radio.modem.IRadioModem.DESCRIPTOR; + break; + case HAL_SERVICE_NETWORK: + serviceName = android.hardware.radio.network.IRadioNetwork.DESCRIPTOR; + break; + case HAL_SERVICE_SIM: + serviceName = android.hardware.radio.sim.IRadioSim.DESCRIPTOR; + break; + case HAL_SERVICE_VOICE: + serviceName = android.hardware.radio.voice.IRadioVoice.DESCRIPTOR; + break; + case HAL_SERVICE_IMS: + serviceName = android.hardware.radio.ims.IRadioIms.DESCRIPTOR; + break; + } + + if (!serviceName.equals("") + && ServiceManager.isDeclared(serviceName + '/' + HIDL_SERVICE_NAME[mPhoneId])) { + return true; + } + + return false; + } + private boolean isRadioBugDetectionEnabled() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ENABLE_RADIO_BUG_DETECTION, 1) != 0; @@ -1205,7 +1333,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.getIccCardStatus(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getIccCardStatus", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getIccCardStatus", e); } } } @@ -1252,7 +1380,7 @@ public class RIL extends BaseCommands implements CommandsInterface { simProxy.supplyIccPinForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPinForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPinForApp", e); } } } @@ -1279,7 +1407,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(newPin), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPukForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPukForApp", e); } } } @@ -1305,7 +1433,7 @@ public class RIL extends BaseCommands implements CommandsInterface { simProxy.supplyIccPin2ForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPin2ForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPin2ForApp", e); } } } @@ -1332,7 +1460,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(newPin2), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPuk2ForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPuk2ForApp", e); } } } @@ -1360,7 +1488,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(newPin), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "changeIccPinForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPinForApp", e); } } } @@ -1388,7 +1516,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(newPin2), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "changeIccPin2ForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPin2ForApp", e); } } } @@ -1410,7 +1538,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(netpin)); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR( - NETWORK_SERVICE, "supplyNetworkDepersonalization", e); + HAL_SERVICE_NETWORK, "supplyNetworkDepersonalization", e); } } } @@ -1420,7 +1548,7 @@ public class RIL extends BaseCommands implements CommandsInterface { Message result) { RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (simProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) { + if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) { RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, result, mRILDefaultWorkSource); @@ -1433,7 +1561,7 @@ public class RIL extends BaseCommands implements CommandsInterface { simProxy.supplySimDepersonalization(rr.mSerial, persoType, RILUtils.convertNullToEmptyString(controlKey)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "supplySimDepersonalization", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplySimDepersonalization", e); } } else { if (PersoSubState.PERSOSUBSTATE_SIM_NETWORK == persoType) { @@ -1465,7 +1593,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getCurrentCalls(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCurrentCalls", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCurrentCalls", e); } } } @@ -1481,7 +1609,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void enableModem(boolean enable, Message result) { RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result); if (modemProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_3)) { + if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_1_3)) { RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_MODEM, result, mRILDefaultWorkSource); if (RILJ_LOGD) { @@ -1492,7 +1620,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.enableModem(rr.mSerial, enable); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "enableModem", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, + "enableModem", e); } } else { if (RILJ_LOGV) riljLog("enableModem: not supported."); @@ -1509,7 +1638,7 @@ public class RIL extends BaseCommands implements CommandsInterface { Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_3)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_3)) { RILRequest rr = obtainRequest(RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, result, mRILDefaultWorkSource); @@ -1521,7 +1650,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setSystemSelectionChannels(rr.mSerial, specifiers); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setSystemSelectionChannels", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "setSystemSelectionChannels", e); } } else { if (RILJ_LOGV) riljLog("setSystemSelectionChannels: not supported."); @@ -1537,7 +1667,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void getSystemSelectionChannels(Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, result, mRILDefaultWorkSource); @@ -1549,7 +1679,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getSystemSelectionChannels(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getSystemSelectionChannels", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "getSystemSelectionChannels", e); } } else { if (RILJ_LOGV) riljLog("getSystemSelectionChannels: not supported."); @@ -1565,7 +1696,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void getModemStatus(Message result) { RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result); if (modemProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_3)) { + if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_1_3)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_MODEM_STATUS, result, mRILDefaultWorkSource); @@ -1576,7 +1707,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.getModemStackStatus(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "getModemStatus", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getModemStatus", e); } } else { if (RILJ_LOGV) riljLog("getModemStatus: not supported."); @@ -1591,7 +1722,8 @@ public class RIL extends BaseCommands implements CommandsInterface { @Override public void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo, boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) { - if (isEmergencyCall && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4) + if (isEmergencyCall + && mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_1_4) && emergencyNumberInfo != null) { emergencyDial(address, emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode, uusInfo, result); @@ -1609,7 +1741,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.dial(rr.mSerial, address, clirMode, uusInfo); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "dial", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "dial", e); } } } @@ -1618,7 +1750,7 @@ public class RIL extends BaseCommands implements CommandsInterface { boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) { RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result); if (voiceProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) { + if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_1_4)) { RILRequest rr = obtainRequest(RIL_REQUEST_EMERGENCY_DIAL, result, mRILDefaultWorkSource); @@ -1631,7 +1763,7 @@ public class RIL extends BaseCommands implements CommandsInterface { voiceProxy.emergencyDial(rr.mSerial, RILUtils.convertNullToEmptyString(address), emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode, uusInfo); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "emergencyDial", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "emergencyDial", e); } } else { riljLoge("emergencyDial is not supported with 1.4 below IRadio"); @@ -1650,13 +1782,13 @@ public class RIL extends BaseCommands implements CommandsInterface { RILRequest rr = obtainRequest(RIL_REQUEST_GET_IMSI, result, mRILDefaultWorkSource); if (RILJ_LOGD) { - riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " aid = " + aid); } try { simProxy.getImsiForApp(rr.mSerial, RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getImsiForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getImsiForApp", e); } } } @@ -1675,7 +1807,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.hangup(rr.mSerial, gsmIndex); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "hangup", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangup", e); } } } @@ -1695,7 +1827,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.hangupWaitingOrBackground(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "hangupWaitingOrBackground", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangupWaitingOrBackground", e); } } } @@ -1716,7 +1848,7 @@ public class RIL extends BaseCommands implements CommandsInterface { voiceProxy.hangupForegroundResumeBackground(rr.mSerial); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR( - VOICE_SERVICE, "hangupForegroundResumeBackground", e); + HAL_SERVICE_VOICE, "hangupForegroundResumeBackground", e); } } } @@ -1735,7 +1867,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.switchWaitingOrHoldingAndActive(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "switchWaitingOrHoldingAndActive", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, + "switchWaitingOrHoldingAndActive", e); } } } @@ -1753,7 +1886,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.conference(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "conference", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "conference", e); } } } @@ -1771,7 +1904,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.rejectCall(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "rejectCall", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "rejectCall", e); } } } @@ -1790,7 +1923,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getLastCallFailCause(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getLastCallFailCause", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getLastCallFailCause", e); } } } @@ -1809,7 +1942,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getSignalStrength(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getSignalStrength", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getSignalStrength", e); } } } @@ -1833,7 +1966,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getVoiceRegistrationState(rr.mSerial, overrideHalVersion); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getVoiceRegistrationState", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRegistrationState", e); } } } @@ -1857,7 +1990,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getDataRegistrationState(rr.mSerial, overrideHalVersion); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getDataRegistrationState", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getDataRegistrationState", e); } } } @@ -1875,7 +2008,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getOperator(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getOperator", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getOperator", e); } } } @@ -1898,7 +2031,7 @@ public class RIL extends BaseCommands implements CommandsInterface { modemProxy.setRadioPower(rr.mSerial, on, forEmergencyCall, preferredForEmergencyCall); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "setRadioPower", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "setRadioPower", e); } } } @@ -1917,7 +2050,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.sendDtmf(rr.mSerial, c + ""); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendDtmf", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendDtmf", e); } } } @@ -1939,7 +2072,7 @@ public class RIL extends BaseCommands implements CommandsInterface { mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM, SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendSMS", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendSMS", e); } } } @@ -1980,7 +2113,7 @@ public class RIL extends BaseCommands implements CommandsInterface { mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM, SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendSMSExpectMore", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendSMSExpectMore", e); } } } @@ -2010,8 +2143,21 @@ public class RIL extends BaseCommands implements CommandsInterface { dataProxy.setupDataCall(rr.mSerial, mPhoneId, accessNetworkType, dataProfile, isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo, trafficDescriptor, matchAllRuleAllowed); - } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "setupDataCall", e); + } catch (RemoteException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setupDataCall", e); + } catch (RuntimeException e) { + riljLoge("setupDataCall RuntimeException: " + e); + int error = RadioError.SYSTEM_ERR; + int responseType = RadioResponseType.SOLICITED; + processResponseInternal(HAL_SERVICE_DATA, rr.mSerial, error, responseType); + processResponseDoneInternal(rr, error, responseType, null); + } + } else { + riljLoge("setupDataCall: DataProxy is empty"); + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(RADIO_NOT_AVAILABLE)); + result.sendToTarget(); } } } @@ -2048,7 +2194,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(pin2), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "iccIoForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccIoForApp", e); } } } @@ -2070,7 +2216,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.sendUssd(rr.mSerial, RILUtils.convertNullToEmptyString(ussd)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendUssd", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendUssd", e); } } } @@ -2089,7 +2235,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.cancelPendingUssd(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "cancelPendingUssd", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "cancelPendingUssd", e); } } } @@ -2107,7 +2253,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getClir(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getClir", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClir", e); } } } @@ -2126,7 +2272,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.setClir(rr.mSerial, clirMode); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "setClir", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setClir", e); } } } @@ -2147,7 +2293,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getCallForwardStatus(rr.mSerial, cfReason, serviceClass, number); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCallForwardStatus", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCallForwardStatus", e); } } } @@ -2170,7 +2316,7 @@ public class RIL extends BaseCommands implements CommandsInterface { voiceProxy.setCallForward( rr.mSerial, action, cfReason, serviceClass, number, timeSeconds); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "setCallForward", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setCallForward", e); } } } @@ -2190,7 +2336,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getCallWaiting(rr.mSerial, serviceClass); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCallWaiting", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCallWaiting", e); } } } @@ -2210,7 +2356,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.setCallWaiting(rr.mSerial, enable, serviceClass); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "setCallWaiting", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setCallWaiting", e); } } } @@ -2231,7 +2377,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.acknowledgeLastIncomingGsmSms(rr.mSerial, success, cause); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "acknowledgeLastIncomingGsmSms", e); } } @@ -2251,7 +2397,7 @@ public class RIL extends BaseCommands implements CommandsInterface { voiceProxy.acceptCall(rr.mSerial); mMetrics.writeRilAnswer(mPhoneId, rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "acceptCall", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "acceptCall", e); } } } @@ -2273,7 +2419,7 @@ public class RIL extends BaseCommands implements CommandsInterface { dataProxy.deactivateDataCall(rr.mSerial, cid, reason); mMetrics.writeRilDeactivateDataCall(mPhoneId, rr.mSerial, cid, reason); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "deactivateDataCall", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "deactivateDataCall", e); } } } @@ -2304,7 +2450,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(password), serviceClass, RILUtils.convertNullToEmptyString(appId)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getFacilityLockForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getFacilityLockForApp", e); } } } @@ -2335,7 +2481,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(password), serviceClass, RILUtils.convertNullToEmptyString(appId)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "setFacilityLockForApp", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setFacilityLockForApp", e); } } } @@ -2360,7 +2506,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(oldPwd), RILUtils.convertNullToEmptyString(newPwd)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "changeBarringPassword", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "changeBarringPassword", e); } } } @@ -2379,7 +2525,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getNetworkSelectionMode(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getNetworkSelectionMode", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getNetworkSelectionMode", e); } } } @@ -2399,7 +2545,7 @@ public class RIL extends BaseCommands implements CommandsInterface { networkProxy.setNetworkSelectionModeAutomatic(rr.mSerial); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR( - NETWORK_SERVICE, "setNetworkSelectionModeAutomatic", e); + HAL_SERVICE_NETWORK, "setNetworkSelectionModeAutomatic", e); } } } @@ -2420,7 +2566,8 @@ public class RIL extends BaseCommands implements CommandsInterface { networkProxy.setNetworkSelectionModeManual(rr.mSerial, RILUtils.convertNullToEmptyString(operatorNumeric), ran); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setNetworkSelectionModeManual", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "setNetworkSelectionModeManual", e); } } } @@ -2439,7 +2586,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getAvailableNetworks(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getAvailableNetworks", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getAvailableNetworks", e); } } } @@ -2454,7 +2601,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void startNetworkScan(NetworkScanRequest networkScanRequest, Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_1)) { HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_START_NETWORK_SCAN); if (RILJ_LOGD) { riljLog("startNetworkScan: overrideHalVersion=" + overrideHalVersion); @@ -2471,7 +2618,7 @@ public class RIL extends BaseCommands implements CommandsInterface { networkProxy.startNetworkScan(rr.mSerial, networkScanRequest, overrideHalVersion, result); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "startNetworkScan", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "startNetworkScan", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startNetworkScan: REQUEST_NOT_SUPPORTED"); @@ -2487,7 +2634,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void stopNetworkScan(Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_1)) { RILRequest rr = obtainRequest(RIL_REQUEST_STOP_NETWORK_SCAN, result, mRILDefaultWorkSource); @@ -2498,7 +2645,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.stopNetworkScan(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "stopNetworkScan", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "stopNetworkScan", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopNetworkScan: REQUEST_NOT_SUPPORTED"); @@ -2524,7 +2671,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.startDtmf(rr.mSerial, c + ""); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "startDtmf", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "startDtmf", e); } } } @@ -2542,7 +2689,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.stopDtmf(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "stopDtmf", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "stopDtmf", e); } } } @@ -2562,7 +2709,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.separateConnection(rr.mSerial, gsmIndex); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "separateConnection", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "separateConnection", e); } } } @@ -2581,7 +2728,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.getBasebandVersion(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "getBasebandVersion", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getBasebandVersion", e); } } } @@ -2600,7 +2747,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.setMute(rr.mSerial, enableMute); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "setMute", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setMute", e); } } } @@ -2618,7 +2765,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getMute(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getMute", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getMute", e); } } } @@ -2636,7 +2783,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getClip(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getClip", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClip", e); } } } @@ -2664,7 +2811,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.getDataCallList(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "getDataCallList", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "getDataCallList", e); } } } @@ -2695,7 +2842,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setSuppServiceNotifications(rr.mSerial, enable); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setSuppServiceNotifications", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "setSuppServiceNotifications", e); } } } @@ -2718,7 +2866,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(smsc), RILUtils.convertNullToEmptyString(pdu)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "writeSmsToSim", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "writeSmsToSim", e); } } } @@ -2739,7 +2887,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.deleteSmsOnSim(rr.mSerial, index); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "deleteSmsOnSim", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnSim", e); } } } @@ -2758,7 +2906,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setBandMode(rr.mSerial, bandMode); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setBandMode", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setBandMode", e); } } } @@ -2777,7 +2925,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getAvailableBandModes(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "queryAvailableBandMode", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "queryAvailableBandMode", e); } } } @@ -2797,7 +2945,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.sendEnvelope(rr.mSerial, RILUtils.convertNullToEmptyString(contents)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "sendEnvelope", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendEnvelope", e); } } } @@ -2819,7 +2967,7 @@ public class RIL extends BaseCommands implements CommandsInterface { simProxy.sendTerminalResponseToSim(rr.mSerial, RILUtils.convertNullToEmptyString(contents)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "sendTerminalResponse", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendTerminalResponse", e); } } } @@ -2840,7 +2988,7 @@ public class RIL extends BaseCommands implements CommandsInterface { simProxy.sendEnvelopeWithStatus(rr.mSerial, RILUtils.convertNullToEmptyString(contents)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "sendEnvelopeWithStatus", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendEnvelopeWithStatus", e); } } } @@ -2859,7 +3007,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.explicitCallTransfer(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "explicitCallTransfer", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "explicitCallTransfer", e); } } } @@ -2881,7 +3029,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setPreferredNetworkTypeBitmap(rr.mSerial, mAllowedNetworkTypesBitmask); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setPreferredNetworkType", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setPreferredNetworkType", e); } } } @@ -2900,7 +3048,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getPreferredNetworkType", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getPreferredNetworkType", e); } } } @@ -2910,7 +3058,7 @@ public class RIL extends BaseCommands implements CommandsInterface { @TelephonyManager.NetworkTypeBitMask int networkTypeBitmask, Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (!networkProxy.isEmpty()) { - if (mRadioVersion.less(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).less(RADIO_HAL_VERSION_1_6)) { // For older HAL, redirects the call to setPreferredNetworkType. setPreferredNetworkType( RadioAccessFamily.getNetworkTypeFromRaf(networkTypeBitmask), result); @@ -2927,7 +3075,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setAllowedNetworkTypesBitmap(rr.mSerial, mAllowedNetworkTypesBitmask); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setAllowedNetworkTypeBitmask", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "setAllowedNetworkTypeBitmask", e); } } } @@ -2946,7 +3095,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getAllowedNetworkTypeBitmask", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "getAllowedNetworkTypeBitmask", e); } } } @@ -2966,7 +3116,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setLocationUpdates(rr.mSerial, enable); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setLocationUpdates", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setLocationUpdates", e); } } } @@ -2978,7 +3128,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void isNrDualConnectivityEnabled(Message result, WorkSource workSource) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED, result, getDefaultWorkSourceIfInvalid(workSource)); @@ -2989,7 +3139,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.isNrDualConnectivityEnabled(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "isNrDualConnectivityEnabled", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "isNrDualConnectivityEnabled", e); } } else { if (RILJ_LOGD) { @@ -3019,7 +3170,7 @@ public class RIL extends BaseCommands implements CommandsInterface { WorkSource workSource) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, result, getDefaultWorkSourceIfInvalid(workSource)); @@ -3031,7 +3182,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setNrDualConnectivityState(rr.mSerial, (byte) nrDualConnectivityState); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "enableNrDualConnectivity", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "enableNrDualConnectivity", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "enableNrDualConnectivity: REQUEST_NOT_SUPPORTED"); @@ -3057,7 +3208,7 @@ public class RIL extends BaseCommands implements CommandsInterface { @Override public void isVoNrEnabled(Message result, WorkSource workSource) { - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result); if (!voiceProxy.isEmpty()) { RILRequest rr = obtainRequest(RIL_REQUEST_IS_VONR_ENABLED , result, @@ -3070,7 +3221,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.isVoNrEnabled(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "isVoNrEnabled", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "isVoNrEnabled", e); } } } else { @@ -3090,7 +3241,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void setVoNrEnabled(boolean enabled, Message result, WorkSource workSource) { setVoNrEnabled(enabled); - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result); if (!voiceProxy.isEmpty()) { RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_VONR, result, @@ -3103,7 +3254,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.setVoNrEnabled(rr.mSerial, enabled); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "setVoNrEnabled", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setVoNrEnabled", e); } } } else { @@ -3135,7 +3286,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.setCdmaSubscriptionSource(rr.mSerial, cdmaSubscription); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "setCdmaSubscriptionSource", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setCdmaSubscriptionSource", e); } } } @@ -3154,7 +3305,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getCdmaRoamingPreference(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "queryCdmaRoamingPreference", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "queryCdmaRoamingPreference", e); } } } @@ -3174,7 +3326,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setCdmaRoamingPreference(rr.mSerial, cdmaRoamingType); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setCdmaRoamingPreference", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCdmaRoamingPreference", e); } } } @@ -3193,7 +3345,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getTtyMode(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getTtyMode", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getTtyMode", e); } } } @@ -3212,7 +3364,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.setTtyMode(rr.mSerial, ttyMode); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "setTtyMode", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setTtyMode", e); } } } @@ -3232,7 +3384,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.setPreferredVoicePrivacy(rr.mSerial, enable); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "setPreferredVoicePrivacy", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setPreferredVoicePrivacy", e); } } } @@ -3251,7 +3403,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.getPreferredVoicePrivacy(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "getPreferredVoicePrivacy", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getPreferredVoicePrivacy", e); } } } @@ -3271,7 +3423,7 @@ public class RIL extends BaseCommands implements CommandsInterface { voiceProxy.sendCdmaFeatureCode(rr.mSerial, RILUtils.convertNullToEmptyString(featureCode)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendCdmaFeatureCode", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendCdmaFeatureCode", e); } } } @@ -3292,7 +3444,7 @@ public class RIL extends BaseCommands implements CommandsInterface { voiceProxy.sendBurstDtmf(rr.mSerial, RILUtils.convertNullToEmptyString(dtmfString), on, off); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendBurstDtmf", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendBurstDtmf", e); } } } @@ -3312,13 +3464,13 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.sendCdmaSmsExpectMore(rr.mSerial, pdu); - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) { + if (mHalVersion.get(HAL_SERVICE_MESSAGING).greaterOrEqual(RADIO_HAL_VERSION_1_5)) { mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA, SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result)); } } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendCdmaSMSExpectMore", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendCdmaSMSExpectMore", e); } } } @@ -3340,7 +3492,7 @@ public class RIL extends BaseCommands implements CommandsInterface { mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA, SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendCdmaSms", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendCdmaSms", e); } } } @@ -3361,7 +3513,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.acknowledgeLastIncomingCdmaSms(rr.mSerial, success, cause); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "acknowledgeLastIncomingCdmaSms", e); } } @@ -3382,7 +3534,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.getGsmBroadcastConfig(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getGsmBroadcastConfig", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getGsmBroadcastConfig", e); } } } @@ -3406,7 +3558,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.setGsmBroadcastConfig(rr.mSerial, config); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setGsmBroadcastConfig", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setGsmBroadcastConfig", e); } } } @@ -3427,7 +3579,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.setGsmBroadcastActivation(rr.mSerial, activate); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setGsmBroadcastActivation", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, + "setGsmBroadcastActivation", e); } } } @@ -3447,7 +3600,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.getCdmaBroadcastConfig(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getCdmaBroadcastConfig", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getCdmaBroadcastConfig", e); } } } @@ -3471,7 +3624,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.setCdmaBroadcastConfig(rr.mSerial, configs); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setCdmaBroadcastConfig", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setCdmaBroadcastConfig", e); } } } @@ -3492,7 +3645,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.setCdmaBroadcastActivation(rr.mSerial, activate); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setCdmaBroadcastActivation", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, + "setCdmaBroadcastActivation", e); } } } @@ -3511,7 +3665,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.getCdmaSubscription(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getCdmaSubscription", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscription", e); } } } @@ -3532,7 +3686,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.writeSmsToRuim(rr.mSerial, status, pdu); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "writeSmsToRuim", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "writeSmsToRuim", e); } } } @@ -3553,7 +3707,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.deleteSmsOnRuim(rr.mSerial, index); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "deleteSmsOnRuim", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnRuim", e); } } } @@ -3572,7 +3726,41 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.getDeviceIdentity(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "getDeviceIdentity", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getDeviceIdentity", e); + } + } + } + + @Override + public void getImei(Message result) { + RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result); + if (modemProxy.isEmpty()) { + if (RILJ_LOGD) { + Rlog.e(RILJ_LOG_TAG, "getImei: modemProxy is Empty"); + } + return; + } + if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_DEVICE_IMEI, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); + } + + try { + modemProxy.getImei(rr.mSerial); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getImei", e); + } + } else { + if (RILJ_LOGD) { + Rlog.e(RILJ_LOG_TAG, "getImei: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); } } } @@ -3591,7 +3779,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { voiceProxy.exitEmergencyCallbackMode(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(VOICE_SERVICE, "exitEmergencyCallbackMode", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "exitEmergencyCallbackMode", e); } } } @@ -3611,7 +3799,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.getSmscAddress(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getSmscAddress", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getSmscAddress", e); } } } @@ -3633,7 +3821,7 @@ public class RIL extends BaseCommands implements CommandsInterface { messagingProxy.setSmscAddress(rr.mSerial, RILUtils.convertNullToEmptyString(address)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setSmscAddress", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setSmscAddress", e); } } } @@ -3654,7 +3842,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { messagingProxy.reportSmsMemoryStatus(rr.mSerial, available); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "reportSmsMemoryStatus", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "reportSmsMemoryStatus", e); } } } @@ -3673,7 +3861,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.reportStkServiceIsRunning(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "reportStkServiceIsRunning", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "reportStkServiceIsRunning", e); } } } @@ -3692,7 +3880,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.getCdmaSubscriptionSource(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getCdmaSubscriptionSource", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscriptionSource", e); } } } @@ -3714,7 +3902,7 @@ public class RIL extends BaseCommands implements CommandsInterface { messagingProxy.acknowledgeIncomingGsmSmsWithPdu(rr.mSerial, success, RILUtils.convertNullToEmptyString(ackPdu)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "acknowledgeIncomingGsmSmsWithPdu", e); } } @@ -3734,7 +3922,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getVoiceRadioTechnology(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getVoiceRadioTechnology", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRadioTechnology", e); } } } @@ -3753,7 +3941,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getCellInfoList(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getCellInfoList", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getCellInfoList", e); } } } @@ -3773,7 +3961,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setCellInfoListRate(rr.mSerial, rateInMillis); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setCellInfoListRate", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCellInfoListRate", e); } } } @@ -3793,7 +3981,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.setInitialAttachApn(rr.mSerial, dataProfile, isRoaming); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "setInitialAttachApn", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setInitialAttachApn", e); } } } @@ -3812,7 +4000,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getImsRegistrationState(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getImsRegistrationState", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getImsRegistrationState", e); } } } @@ -3835,7 +4023,7 @@ public class RIL extends BaseCommands implements CommandsInterface { mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS, SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendImsGsmSms", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendImsGsmSms", e); } } } @@ -3857,7 +4045,7 @@ public class RIL extends BaseCommands implements CommandsInterface { mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS, SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendImsCdmaSms", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendImsCdmaSms", e); } } } @@ -3885,7 +4073,7 @@ public class RIL extends BaseCommands implements CommandsInterface { simProxy.iccTransmitApduBasicChannel( rr.mSerial, cla, instruction, p1, p2, p3, data); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "iccTransmitApduBasicChannel", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccTransmitApduBasicChannel", e); } } } @@ -3910,13 +4098,13 @@ public class RIL extends BaseCommands implements CommandsInterface { simProxy.iccOpenLogicalChannel(rr.mSerial, RILUtils.convertNullToEmptyString(aid), p2); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "iccOpenLogicalChannel", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccOpenLogicalChannel", e); } } } @Override - public void iccCloseLogicalChannel(int channel, Message result) { + public void iccCloseLogicalChannel(int channel, boolean isEs10, Message result) { RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (!simProxy.isEmpty()) { RILRequest rr = obtainRequest(RIL_REQUEST_SIM_CLOSE_CHANNEL, result, @@ -3924,20 +4112,19 @@ public class RIL extends BaseCommands implements CommandsInterface { if (RILJ_LOGD) { riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) - + " channel = " + channel); + + " channel = " + channel + " isEs10 = " + isEs10); } - try { - simProxy.iccCloseLogicalChannel(rr.mSerial, channel); + simProxy.iccCloseLogicalChannel(rr.mSerial, channel, isEs10); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "iccCloseLogicalChannel", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccCloseLogicalChannel", e); } } } @Override public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, int p2, - int p3, String data, Message result) { + int p3, String data, boolean isEs10Command, Message result) { if (channel <= 0) { throw new RuntimeException( "Invalid channel in iccTransmitApduLogicalChannel: " + channel); @@ -3954,6 +4141,7 @@ public class RIL extends BaseCommands implements CommandsInterface { + String.format(" channel = %d", channel) + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction) + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3) + + " isEs10Command = " + isEs10Command + " data = " + data); } else { riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); @@ -3962,9 +4150,9 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.iccTransmitApduLogicalChannel( - rr.mSerial, channel, cla, instruction, p1, p2, p3, data); + rr.mSerial, channel, cla, instruction, p1, p2, p3, data, isEs10Command); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "iccTransmitApduLogicalChannel", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccTransmitApduLogicalChannel", e); } } } @@ -3984,7 +4172,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.nvReadItem(rr.mSerial, itemID); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvReadItem", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvReadItem", e); } } } @@ -4005,7 +4193,7 @@ public class RIL extends BaseCommands implements CommandsInterface { modemProxy.nvWriteItem(rr.mSerial, itemId, RILUtils.convertNullToEmptyString(itemValue)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvWriteItem", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvWriteItem", e); } } } @@ -4026,7 +4214,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.nvWriteCdmaPrl(rr.mSerial, preferredRoamingList); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvWriteCdmaPrl", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvWriteCdmaPrl", e); } } } @@ -4046,7 +4234,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.nvResetConfig(rr.mSerial, resetType); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvResetConfig", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvResetConfig", e); } } } @@ -4068,7 +4256,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.setUiccSubscription(rr.mSerial, slotId, appIndex, subId, subStatus); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "setUiccSubscription", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setUiccSubscription", e); } } } @@ -4083,7 +4271,7 @@ public class RIL extends BaseCommands implements CommandsInterface { // EID should be supported as long as HAL >= 1.2. // - in HAL 1.2 we have EID through ATR // - in later HAL versions we also have EID through slot / card status. - return mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2); + return mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2); } @Override @@ -4100,7 +4288,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.setDataAllowed(rr.mSerial, allowed); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "setDataAllowed", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataAllowed", e); } } } @@ -4120,7 +4308,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.getHardwareConfig(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "getHardwareConfig", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getHardwareConfig", e); } } } @@ -4143,7 +4331,7 @@ public class RIL extends BaseCommands implements CommandsInterface { RILUtils.convertNullToEmptyString(data), RILUtils.convertNullToEmptyString(aid)); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "requestIccSimAuthentication", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "requestIccSimAuthentication", e); } } } @@ -4166,7 +4354,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.setDataProfile(rr.mSerial, dps, isRoaming); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "setDataProfile", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataProfile", e); } } } @@ -4184,7 +4372,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.requestShutdown(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "requestShutdown", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "requestShutdown", e); } } } @@ -4203,7 +4391,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.getRadioCapability(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "getRadioCapability", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getRadioCapability", e); } } } @@ -4223,14 +4411,14 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.setRadioCapability(rr.mSerial, rc); } catch (Exception e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "setRadioCapability", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "setRadioCapability", e); } } } @Override public void startLceService(int reportIntervalMs, boolean pullMode, Message result) { - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) { + if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) { // We have a 1.2 or later radio, so the LCE 1.0 LCE service control path is unused. // Instead the LCE functionality is always-on and provides unsolicited indications. if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startLceService: REQUEST_NOT_SUPPORTED"); @@ -4254,14 +4442,14 @@ public class RIL extends BaseCommands implements CommandsInterface { try { radioProxy.startLceService(rr.mSerial, reportIntervalMs, pullMode); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(RADIO_SERVICE, "startLceService", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "startLceService", e); } } } @Override public void stopLceService(Message result) { - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) { + if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) { // We have a 1.2 or later radio, so the LCE 1.0 LCE service control is unused. // Instead the LCE functionality is always-on and provides unsolicited indications. if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopLceService: REQUEST_NOT_SUPPORTED"); @@ -4284,7 +4472,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { radioProxy.stopLceService(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(RADIO_SERVICE, "stopLceService", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "stopLceService", e); } } } @@ -4303,7 +4491,7 @@ public class RIL extends BaseCommands implements CommandsInterface { long completionWindowMillis) { RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_SET_DATA_THROTTLING, result, getDefaultWorkSourceIfInvalid(workSource)); @@ -4318,7 +4506,7 @@ public class RIL extends BaseCommands implements CommandsInterface { dataProxy.setDataThrottling(rr.mSerial, (byte) dataThrottlingAction, completionWindowMillis); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "setDataThrottling", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataThrottling", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "setDataThrottling: REQUEST_NOT_SUPPORTED"); @@ -4343,7 +4531,7 @@ public class RIL extends BaseCommands implements CommandsInterface { @Deprecated @Override public void pullLceData(Message result) { - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) { + if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) { // We have a 1.2 or later radio, so the LCE 1.0 LCE service control path is unused. // Instead the LCE functionality is always-on and provides unsolicited indications. if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "pullLceData: REQUEST_NOT_SUPPORTED"); @@ -4366,7 +4554,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { radioProxy.pullLceData(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(RADIO_SERVICE, "pullLceData", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "pullLceData", e); } } } @@ -4388,7 +4576,7 @@ public class RIL extends BaseCommands implements CommandsInterface { mRilHandler.obtainMessage(EVENT_BLOCKING_RESPONSE_TIMEOUT, rr.mSerial); mRilHandler.sendMessageDelayed(msg, DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "getModemActivityInfo", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getModemActivityInfo", e); } } } @@ -4411,7 +4599,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules, result); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "setAllowedCarriers", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setAllowedCarriers", e); } } } @@ -4430,7 +4618,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.getAllowedCarriers(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getAllowedCarriers", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getAllowedCarriers", e); } } } @@ -4450,7 +4638,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { modemProxy.sendDeviceState(rr.mSerial, stateType, state); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(MODEM_SERVICE, "sendDeviceState", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "sendDeviceState", e); } } } @@ -4470,7 +4658,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setIndicationFilter(rr.mSerial, filter); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setIndicationFilter", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setIndicationFilter", e); } } } @@ -4480,7 +4668,7 @@ public class RIL extends BaseCommands implements CommandsInterface { @NonNull List signalThresholdInfos, @Nullable Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_2)) { RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA, result, mRILDefaultWorkSource); @@ -4491,7 +4679,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setSignalStrengthReportingCriteria(rr.mSerial, signalThresholdInfos); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setSignalStrengthReportingCriteria", e); } } else { @@ -4505,7 +4693,7 @@ public class RIL extends BaseCommands implements CommandsInterface { Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_2)) { RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, result, mRILDefaultWorkSource); @@ -4519,7 +4707,7 @@ public class RIL extends BaseCommands implements CommandsInterface { ran); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR( - NETWORK_SERVICE, "setLinkCapacityReportingCriteria", e); + HAL_SERVICE_NETWORK, "setLinkCapacityReportingCriteria", e); } } else { riljLoge("setLinkCapacityReportingCriteria ignored on IRadio version less than 1.2"); @@ -4541,7 +4729,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.setSimCardPower(rr.mSerial, state, result); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "setSimCardPower", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setSimCardPower", e); } } } @@ -4552,7 +4740,7 @@ public class RIL extends BaseCommands implements CommandsInterface { Objects.requireNonNull(imsiEncryptionInfo, "ImsiEncryptionInfo cannot be null."); RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (simProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) { + if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_1)) { RILRequest rr = obtainRequest(RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, result, mRILDefaultWorkSource); if (RILJ_LOGD) { @@ -4562,7 +4750,8 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.setCarrierInfoForImsiEncryption(rr.mSerial, imsiEncryptionInfo); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "setCarrierInfoForImsiEncryption", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, + "setCarrierInfoForImsiEncryption", e); } } else { if (RILJ_LOGD) { @@ -4582,7 +4771,7 @@ public class RIL extends BaseCommands implements CommandsInterface { Objects.requireNonNull(packetData, "KeepaliveRequest cannot be null."); RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_1)) { RILRequest rr = obtainRequest(RIL_REQUEST_START_KEEPALIVE, result, mRILDefaultWorkSource); @@ -4593,7 +4782,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.startKeepalive(rr.mSerial, contextId, packetData, intervalMillis, result); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "startNattKeepalive", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "startNattKeepalive", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startNattKeepalive: REQUEST_NOT_SUPPORTED"); @@ -4609,7 +4798,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void stopNattKeepalive(int sessionHandle, Message result) { RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_1)) { RILRequest rr = obtainRequest(RIL_REQUEST_STOP_KEEPALIVE, result, mRILDefaultWorkSource); @@ -4620,7 +4809,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.stopKeepalive(rr.mSerial, sessionHandle); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "stopNattKeepalive", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "stopNattKeepalive", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopNattKeepalive: REQUEST_NOT_SUPPORTED"); @@ -4669,7 +4858,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void enableUiccApplications(boolean enable, Message result) { RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (simProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) { + if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) { RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_UICC_APPLICATIONS, result, mRILDefaultWorkSource); @@ -4681,7 +4870,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.enableUiccApplications(rr.mSerial, enable); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "enableUiccApplications", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "enableUiccApplications", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "enableUiccApplications: REQUEST_NOT_SUPPORTED"); @@ -4702,7 +4891,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void areUiccApplicationsEnabled(Message result) { RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (simProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) { + if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT, result, mRILDefaultWorkSource); @@ -4713,7 +4902,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.areUiccApplicationsEnabled(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "areUiccApplicationsEnabled", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "areUiccApplicationsEnabled", e); } } else { if (RILJ_LOGD) { @@ -4733,7 +4922,7 @@ public class RIL extends BaseCommands implements CommandsInterface { @Override public boolean canToggleUiccApplicationsEnablement() { return !getRadioServiceProxy(RadioSimProxy.class, null).isEmpty() - && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5); + && mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5); } @Override @@ -4759,7 +4948,7 @@ public class RIL extends BaseCommands implements CommandsInterface { voiceProxy.handleStkCallSetupRequestFromSim(rr.mSerial, accept); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR( - VOICE_SERVICE, "handleStkCallSetupRequestFromSim", e); + HAL_SERVICE_VOICE, "handleStkCallSetupRequestFromSim", e); } } } @@ -4771,7 +4960,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void getBarringInfo(Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_5)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_BARRING_INFO, result, mRILDefaultWorkSource); @@ -4782,7 +4971,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getBarringInfo(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getBarringInfo", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getBarringInfo", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getBarringInfo: REQUEST_NOT_SUPPORTED"); @@ -4801,7 +4990,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void allocatePduSessionId(Message result) { RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, result, mRILDefaultWorkSource); if (RILJ_LOGD) { @@ -4811,7 +5000,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.allocatePduSessionId(rr.mSerial); } catch (RemoteException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "allocatePduSessionId", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "allocatePduSessionId", e); } } else { AsyncResult.forMessage(result, null, @@ -4827,7 +5016,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void releasePduSessionId(Message result, int pduSessionId) { RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_RELEASE_PDU_SESSION_ID, result, mRILDefaultWorkSource); if (RILJ_LOGD) { @@ -4837,7 +5026,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.releasePduSessionId(rr.mSerial, pduSessionId); } catch (RemoteException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "releasePduSessionId", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "releasePduSessionId", e); } } else { AsyncResult.forMessage(result, null, @@ -4853,7 +5042,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void startHandover(Message result, int callId) { RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_START_HANDOVER, result, mRILDefaultWorkSource); if (RILJ_LOGD) { @@ -4863,7 +5052,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.startHandover(rr.mSerial, callId); } catch (RemoteException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "startHandover", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "startHandover", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startHandover: REQUEST_NOT_SUPPORTED"); @@ -4882,7 +5071,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void cancelHandover(Message result, int callId) { RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_HANDOVER, result, mRILDefaultWorkSource); if (RILJ_LOGD) { @@ -4892,7 +5081,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.cancelHandover(rr.mSerial, callId); } catch (RemoteException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "cancelHandover", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "cancelHandover", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "cancelHandover: REQUEST_NOT_SUPPORTED"); @@ -4909,7 +5098,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void getSlicingConfig(Message result) { RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result); if (dataProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLICING_CONFIG, result, mRILDefaultWorkSource); @@ -4920,7 +5109,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { dataProxy.getSlicingConfig(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(DATA_SERVICE, "getSlicingConfig", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "getSlicingConfig", e); } } else { if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getSlicingConfig: REQUEST_NOT_SUPPORTED"); @@ -4934,7 +5123,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void getSimPhonebookRecords(Message result) { RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (simProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, result, mRILDefaultWorkSource); @@ -4945,7 +5134,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.getSimPhonebookRecords(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getSimPhonebookRecords", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getSimPhonebookRecords", e); } } else { if (RILJ_LOGD) { @@ -4963,7 +5152,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void getSimPhonebookCapacity(Message result) { RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (simProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, result, mRILDefaultWorkSource); @@ -4974,7 +5163,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.getSimPhonebookCapacity(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "getSimPhonebookCapacity", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getSimPhonebookCapacity", e); } } else { if (RILJ_LOGD) { @@ -4992,7 +5181,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) { RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result); if (simProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) { + if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) { RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD, result, mRILDefaultWorkSource); @@ -5004,7 +5193,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { simProxy.updateSimPhonebookRecords(rr.mSerial, phonebookRecord); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(SIM_SERVICE, "updateSimPhonebookRecords", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "updateSimPhonebookRecords", e); } } else { if (RILJ_LOGD) { @@ -5029,7 +5218,7 @@ public class RIL extends BaseCommands implements CommandsInterface { /* @TelephonyManager.UsageSetting */ int usageSetting) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { RILRequest rr = obtainRequest(RIL_REQUEST_SET_USAGE_SETTING, result, mRILDefaultWorkSource); @@ -5040,7 +5229,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.setUsageSetting(rr.mSerial, usageSetting); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setUsageSetting", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setUsageSetting", e); } } else { if (RILJ_LOGD) { @@ -5063,7 +5252,7 @@ public class RIL extends BaseCommands implements CommandsInterface { public void getUsageSetting(Message result) { RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); if (networkProxy.isEmpty()) return; - if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { RILRequest rr = obtainRequest(RIL_REQUEST_GET_USAGE_SETTING, result, mRILDefaultWorkSource); @@ -5074,7 +5263,7 @@ public class RIL extends BaseCommands implements CommandsInterface { try { networkProxy.getUsageSetting(rr.mSerial); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getUsageSetting", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getUsageSetting", e); } } else { if (RILJ_LOGD) { @@ -5088,125 +5277,901 @@ public class RIL extends BaseCommands implements CommandsInterface { } } - //***** Private Methods - /** - * This is a helper function to be called when an indication callback is called for any radio - * service. It takes care of acquiring wakelock and sending ack if needed. - * @param service radio service the indication is for - * @param indicationType indication type received - */ - void processIndication(int service, int indicationType) { - if (indicationType == RadioIndicationType.UNSOLICITED_ACK_EXP) { - sendAck(service); - if (RILJ_LOGD) riljLog("Unsol response received; Sending ack to ril.cpp"); + @Override + public void setSrvccCallInfo(SrvccConnection[] srvccConnections, Message result) { + RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result); + if (imsProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + RILRequest rr = obtainRequest(RIL_REQUEST_SET_SRVCC_CALL_INFO, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + // Do not log function arg for privacy + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); + } + + try { + imsProxy.setSrvccCallInfo(rr.mSerial, + RILUtils.convertToHalSrvccCall(srvccConnections)); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "setSrvccCallInfo", e); + } } else { - // ack is not expected to be sent back. Nothing is required to be done here. + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "setSrvccCallInfo: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } } } - void processRequestAck(int serial) { - RILRequest rr; - synchronized (mRequestList) { - rr = mRequestList.get(serial); - } - if (rr == null) { - Rlog.w(RILJ_LOG_TAG, "processRequestAck: Unexpected solicited ack response! " - + "serial: " + serial); + @Override + public void updateImsRegistrationInfo( + @RegistrationManager.ImsRegistrationState int state, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech, + @RegistrationManager.SuggestedAction int suggestedAction, + int capabilities, Message result) { + RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result); + if (imsProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + " state=" + state + ", radioTech=" + imsRadioTech + + ", suggested=" + suggestedAction + ", cap=" + capabilities); + } + + android.hardware.radio.ims.ImsRegistration registrationInfo = + new android.hardware.radio.ims.ImsRegistration(); + registrationInfo.regState = RILUtils.convertImsRegistrationState(state); + registrationInfo.accessNetworkType = RILUtils.convertImsRegistrationTech(imsRadioTech); + registrationInfo.suggestedAction = suggestedAction; + registrationInfo.capabilities = RILUtils.convertImsCapability(capabilities); + + try { + imsProxy.updateImsRegistrationInfo(rr.mSerial, registrationInfo); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "updateImsRegistrationInfo", e); + } } else { - decrementWakeLock(rr); if (RILJ_LOGD) { - riljLog(rr.serialString() + " Ack < " + RILUtils.requestToString(rr.mRequest)); + Rlog.d(RILJ_LOG_TAG, "updateImsRegistrationInfo: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); } } } - /** - * This is a helper function to be called when a RadioResponse callback is called. - * It takes care of acks, wakelocks, and finds and returns RILRequest corresponding to the - * response if one is found. - * @param responseInfo RadioResponseInfo received in response callback - * @return RILRequest corresponding to the response - */ - @VisibleForTesting - public RILRequest processResponse(RadioResponseInfo responseInfo) { - return processResponseInternal(RADIO_SERVICE, responseInfo.serial, responseInfo.error, - responseInfo.type); - } + @Override + public void startImsTraffic(int token, + int trafficType, int accessNetworkType, int trafficDirection, Message result) { + RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result); + if (imsProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + RILRequest rr = obtainRequest(RIL_REQUEST_START_IMS_TRAFFIC, result, + mRILDefaultWorkSource); - /** - * This is a helper function for V1_6.RadioResponseInfo to be called when a RadioResponse - * callback is called. It takes care of acks, wakelocks, and finds and returns RILRequest - * corresponding to the response if one is found. - * @param responseInfo RadioResponseInfo received in response callback - * @return RILRequest corresponding to the response - */ - @VisibleForTesting - public RILRequest processResponse_1_6( - android.hardware.radio.V1_6.RadioResponseInfo responseInfo) { - return processResponseInternal(RADIO_SERVICE, responseInfo.serial, responseInfo.error, - responseInfo.type); - } + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + "{" + token + ", " + trafficType + ", " + + accessNetworkType + ", " + trafficDirection + "}"); + } - /** - * This is a helper function for an AIDL RadioResponseInfo to be called when a RadioResponse - * callback is called. It takes care of acks, wakelocks, and finds and returns RILRequest - * corresponding to the response if one is found. - * @param service Radio service that received the response - * @param responseInfo RadioResponseInfo received in response callback - * @return RILRequest corresponding to the response - */ - @VisibleForTesting - public RILRequest processResponse(int service, - android.hardware.radio.RadioResponseInfo responseInfo) { - return processResponseInternal(service, responseInfo.serial, responseInfo.error, - responseInfo.type); + try { + imsProxy.startImsTraffic(rr.mSerial, token, + RILUtils.convertImsTrafficType(trafficType), accessNetworkType, + RILUtils.convertImsTrafficDirection(trafficDirection)); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "startImsTraffic", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "startImsTraffic: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } } - private RILRequest processResponseInternal(int service, int serial, int error, int type) { - RILRequest rr; + @Override + public void stopImsTraffic(int token, Message result) { + RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result); + if (imsProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + RILRequest rr = obtainRequest(RIL_REQUEST_STOP_IMS_TRAFFIC, result, + mRILDefaultWorkSource); - if (type == RadioResponseType.SOLICITED_ACK) { - synchronized (mRequestList) { - rr = mRequestList.get(serial); + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + "{" + token + "}"); } - if (rr == null) { - Rlog.w(RILJ_LOG_TAG, "Unexpected solicited ack response! sn: " + serial); - } else { - decrementWakeLock(rr); - if (mRadioBugDetector != null) { - mRadioBugDetector.detectRadioBug(rr.mRequest, error); - } - if (RILJ_LOGD) { - riljLog(rr.serialString() + " Ack from " + serviceToString(service) - + " < " + RILUtils.requestToString(rr.mRequest)); - } + + try { + imsProxy.stopImsTraffic(rr.mSerial, token); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "stopImsTraffic", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "stopImsTraffic: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); } - return rr; } + } - rr = findAndRemoveRequestFromList(serial); - if (rr == null) { - Rlog.e(RILJ_LOG_TAG, "processResponse: Unexpected response! serial: " + serial - + " ,error: " + error); - return null; - } - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_NETWORK, "RIL", "" /* unused */, rr.mSerial); + @Override + public void triggerEpsFallback(int reason, Message result) { + RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result); + if (imsProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EPS_FALLBACK, result, + mRILDefaultWorkSource); - // Time logging for RIL command and storing it in TelephonyHistogram. - addToRilHistogram(rr); - if (mRadioBugDetector != null) { - mRadioBugDetector.detectRadioBug(rr.mRequest, error); - } - if (type == RadioResponseType.SOLICITED_ACK_EXP) { - sendAck(service); if (RILJ_LOGD) { - riljLog("Response received from " + serviceToString(service) + " for " - + rr.serialString() + " " + RILUtils.requestToString(rr.mRequest) - + " Sending ack to ril.cpp"); + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + " reason=" + reason); } - } else { - // ack sent for SOLICITED_ACK_EXP above; nothing to do for SOLICITED response - } + + try { + imsProxy.triggerEpsFallback(rr.mSerial, reason); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "triggerEpsFallback", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "triggerEpsFallback: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + @Override + public void sendAnbrQuery(int mediaType, int direction, int bitsPerSecond, + Message result) { + RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result); + if (imsProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + RILRequest rr = obtainRequest(RIL_REQUEST_SEND_ANBR_QUERY, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); + } + + try { + imsProxy.sendAnbrQuery(rr.mSerial, mediaType, direction, bitsPerSecond); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "sendAnbrQuery", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "sendAnbrQuery: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setEmergencyMode(int emcMode, Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_SET_EMERGENCY_MODE, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + " mode=" + EmergencyConstants.emergencyModeToString(emcMode)); + } + + try { + networkProxy.setEmergencyMode(rr.mSerial, emcMode); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setEmergencyMode", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "setEmergencyMode: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void triggerEmergencyNetworkScan( + @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork, + @DomainSelectionService.EmergencyScanType int scanType, Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + " networkType=" + RILUtils.accessNetworkTypesToString(accessNetwork) + + ", scanType=" + RILUtils.scanTypeToString(scanType)); + } + + try { + networkProxy.triggerEmergencyNetworkScan(rr.mSerial, + RILUtils.convertEmergencyNetworkScanTrigger(accessNetwork, scanType)); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "triggerEmergencyNetworkScan", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "triggerEmergencyNetworkScan: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void cancelEmergencyNetworkScan(boolean resetScan, Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + " resetScan=" + resetScan); + } + + try { + networkProxy.cancelEmergencyNetworkScan(rr.mSerial, resetScan); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, + "cancelEmergencyNetworkScan", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "cancelEmergencyNetworkScan: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void exitEmergencyMode(Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_EXIT_EMERGENCY_MODE, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); + } + + try { + networkProxy.exitEmergencyMode(rr.mSerial); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "exitEmergencyMode", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "exitEmergencyMode: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * Set if null ciphering / null integrity modes are permitted. + * + * @param result Callback message containing the success or failure status. + * @param enabled true if null ciphering / null integrity modes are permitted, false otherwise + */ + @Override + public void setNullCipherAndIntegrityEnabled(boolean enabled, Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); + } + + try { + networkProxy.setNullCipherAndIntegrityEnabled(rr.mSerial, enabled); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR( + HAL_SERVICE_NETWORK, "setNullCipherAndIntegrityEnabled", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "setNullCipherAndIntegrityEnabled: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * Get if null ciphering / null integrity are enabled / disabled. + * + * @param result Callback message containing the success or failure status. + */ + @Override + public void isNullCipherAndIntegrityEnabled(Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); + } + + try { + networkProxy.isNullCipherAndIntegrityEnabled(rr.mSerial); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR( + HAL_SERVICE_NETWORK, "isNullCipherAndIntegrityEnabled", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "isNullCipherAndIntegrityEnabled: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void updateImsCallStatus(@NonNull List imsCallInfo, Message result) { + RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result); + if (imsProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) { + RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_CALL_STATUS, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + " " + imsCallInfo); + } + try { + imsProxy.updateImsCallStatus(rr.mSerial, RILUtils.convertImsCallInfo(imsCallInfo)); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "updateImsCallStatus", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "updateImsCallStatus: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setN1ModeEnabled(boolean enable, Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_SET_N1_MODE_ENABLED, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + + " enable=" + enable); + } + + try { + networkProxy.setN1ModeEnabled(rr.mSerial, enable); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setN1ModeEnabled", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "setN1ModeEnabled: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void isN1ModeEnabled(Message result) { + RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result); + if (networkProxy.isEmpty()) return; + if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) { + RILRequest rr = obtainRequest(RIL_REQUEST_IS_N1_MODE_ENABLED, result, + mRILDefaultWorkSource); + + if (RILJ_LOGD) { + riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)); + } + + try { + networkProxy.isN1ModeEnabled(rr.mSerial); + } catch (RemoteException | RuntimeException e) { + handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "isN1ModeEnabled", e); + } + } else { + if (RILJ_LOGD) { + Rlog.d(RILJ_LOG_TAG, "isN1ModeEnabled: REQUEST_NOT_SUPPORTED"); + } + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + } + + /** + * Get feature capabilities supported by satellite. + * + * @param result Message that will be sent back to the requester + */ + @Override + public void getSatelliteCapabilities(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Turn satellite modem on/off. + * + * @param result Message that will be sent back to the requester + * @param on True for turning on. + * False for turning off. + */ + @Override + public void setSatellitePower(Message result, boolean on) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Get satellite modem state. + * + * @param result Message that will be sent back to the requester + */ + @Override + public void getSatellitePowerState(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Get satellite provision state. + * + * @param result Message that will be sent back to the requester + */ + @Override + public void getSatelliteProvisionState(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Provision the subscription with a satellite provider. This is needed to register the + * subscription if the provider allows dynamic registration. + * + * @param result Message that will be sent back to the requester. + * @param imei IMEI of the SIM associated with the satellite modem. + * @param msisdn MSISDN of the SIM associated with the satellite modem. + * @param imsi IMSI of the SIM associated with the satellite modem. + * @param features List of features to be provisioned. + */ + @Override + public void provisionSatelliteService( + Message result, String imei, String msisdn, String imsi, int[] features) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Add contacts that are allowed to be used for satellite communication. This is applicable for + * incoming messages as well. + * + * @param result Message that will be sent back to the requester. + * @param contacts List of allowed contacts to be added. + */ + @Override + public void addAllowedSatelliteContacts(Message result, String[] contacts) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Remove contacts that are allowed to be used for satellite communication. This is applicable + * for incoming messages as well. + * + * @param result Message that will be sent back to the requester. + * @param contacts List of allowed contacts to be removed. + */ + @Override + public void removeAllowedSatelliteContacts(Message result, String[] contacts) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Send text messages. + * + * @param result Message that will be sent back to the requester. + * @param messages List of messages in text format to be sent. + * @param destination The recipient of the message. + * @param latitude The current latitude of the device. + * @param longitude The current longitude of the device. + */ + @Override + public void sendSatelliteMessages(Message result, String[] messages, String destination, + double latitude, double longitude) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Get pending messages. + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void getPendingSatelliteMessages(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Get current satellite registration mode. + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void getSatelliteMode(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Set the filter for what type of indication framework want to receive from modem. + * + * @param result Message that will be sent back to the requester. + * @param filterBitmask The filter bitmask identifying what type of indication framework want to + * receive from modem. + */ + @Override + public void setSatelliteIndicationFilter(Message result, int filterBitmask) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Check whether satellite modem is supported by the device. + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void isSatelliteSupported(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * User started pointing to the satellite. Modem should continue to update the ponting input + * as user moves device. + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void startSendingSatellitePointingInfo(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Stop pointing to satellite indications. + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void stopSendingSatellitePointingInfo(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Get max text limit for messaging per message. + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void getMaxCharactersPerSatelliteTextMessage(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Get whether satellite communication is allowed for the current location + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void isSatelliteCommunicationAllowedForCurrentLocation(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + /** + * Get time for next visibility of satellite. + * + * @param result Message that will be sent back to the requester. + */ + @Override + public void getTimeForNextSatelliteVisibility(Message result) { + // Satellite HAL APIs are not supported before Android V. + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } + + //***** Private Methods + /** + * This is a helper function to be called when an indication callback is called for any radio + * service. It takes care of acquiring wakelock and sending ack if needed. + * @param service radio service the indication is for + * @param indicationType indication type received + */ + void processIndication(int service, int indicationType) { + if (indicationType == RadioIndicationType.UNSOLICITED_ACK_EXP) { + sendAck(service); + if (RILJ_LOGD) riljLog("Unsol response received; Sending ack to ril.cpp"); + } else { + // ack is not expected to be sent back. Nothing is required to be done here. + } + } + + void processRequestAck(int serial) { + RILRequest rr; + synchronized (mRequestList) { + rr = mRequestList.get(serial); + } + if (rr == null) { + Rlog.w(RILJ_LOG_TAG, "processRequestAck: Unexpected solicited ack response! " + + "serial: " + serial); + } else { + decrementWakeLock(rr); + if (RILJ_LOGD) { + riljLog(rr.serialString() + " Ack < " + RILUtils.requestToString(rr.mRequest)); + } + } + } + + /** + * This is a helper function to be called when a RadioResponse callback is called. + * It takes care of acks, wakelocks, and finds and returns RILRequest corresponding to the + * response if one is found. + * @param responseInfo RadioResponseInfo received in response callback + * @return RILRequest corresponding to the response + */ + @VisibleForTesting + public RILRequest processResponse(RadioResponseInfo responseInfo) { + return processResponseInternal(HAL_SERVICE_RADIO, responseInfo.serial, responseInfo.error, + responseInfo.type); + } + + /** + * This is a helper function for V1_6.RadioResponseInfo to be called when a RadioResponse + * callback is called. It takes care of acks, wakelocks, and finds and returns RILRequest + * corresponding to the response if one is found. + * @param responseInfo RadioResponseInfo received in response callback + * @return RILRequest corresponding to the response + */ + @VisibleForTesting + public RILRequest processResponse_1_6( + android.hardware.radio.V1_6.RadioResponseInfo responseInfo) { + return processResponseInternal(HAL_SERVICE_RADIO, responseInfo.serial, responseInfo.error, + responseInfo.type); + } + + /** + * This is a helper function for an AIDL RadioResponseInfo to be called when a RadioResponse + * callback is called. It takes care of acks, wakelocks, and finds and returns RILRequest + * corresponding to the response if one is found. + * @param service Radio service that received the response + * @param responseInfo RadioResponseInfo received in response callback + * @return RILRequest corresponding to the response + */ + public RILRequest processResponse(int service, + android.hardware.radio.RadioResponseInfo responseInfo) { + return processResponseInternal(service, responseInfo.serial, responseInfo.error, + responseInfo.type); + } + + private RILRequest processResponseInternal(int service, int serial, int error, int type) { + RILRequest rr; + + if (type == RadioResponseType.SOLICITED_ACK) { + synchronized (mRequestList) { + rr = mRequestList.get(serial); + } + if (rr == null) { + Rlog.w(RILJ_LOG_TAG, "Unexpected solicited ack response! sn: " + serial); + } else { + decrementWakeLock(rr); + if (mRadioBugDetector != null) { + mRadioBugDetector.detectRadioBug(rr.mRequest, error); + } + if (RILJ_LOGD) { + riljLog(rr.serialString() + " Ack from " + serviceToString(service) + + " < " + RILUtils.requestToString(rr.mRequest)); + } + } + return rr; + } + + rr = findAndRemoveRequestFromList(serial); + if (rr == null) { + Rlog.e(RILJ_LOG_TAG, "processResponse: Unexpected response! serial: " + serial + + " ,error: " + error); + return null; + } + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial); + + // Time logging for RIL command and storing it in TelephonyHistogram. + addToRilHistogram(rr); + if (mRadioBugDetector != null) { + mRadioBugDetector.detectRadioBug(rr.mRequest, error); + } + if (type == RadioResponseType.SOLICITED_ACK_EXP) { + sendAck(service); + if (RILJ_LOGD) { + riljLog("Response received from " + serviceToString(service) + " for " + + rr.serialString() + " " + RILUtils.requestToString(rr.mRequest) + + " Sending ack to ril.cpp"); + } + } else { + // ack sent for SOLICITED_ACK_EXP above; nothing to do for SOLICITED response + } // Here and below fake RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED, see b/7255789. // This is needed otherwise we don't automatically transition to the main lock @@ -5355,13 +6320,13 @@ public class RIL extends BaseCommands implements CommandsInterface { RILRequest rr = RILRequest.obtain(RIL_RESPONSE_ACKNOWLEDGEMENT, null, mRILDefaultWorkSource); acquireWakeLock(rr, FOR_ACK_WAKELOCK); - if (service == RADIO_SERVICE) { + if (service == HAL_SERVICE_RADIO) { IRadio radioProxy = getRadioProxy(null); if (radioProxy != null) { try { radioProxy.responseAcknowledgement(); } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(RADIO_SERVICE, "sendAck", e); + handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "sendAck", e); riljLoge("sendAck: " + e); } } else { @@ -5506,7 +6471,7 @@ public class RIL extends BaseCommands implements CommandsInterface { synchronized (mWakeLock) { if (mWakeLockCount == 0 && !mWakeLock.isHeld()) return false; Rlog.d(RILJ_LOG_TAG, "NOTE: mWakeLockCount is " + mWakeLockCount - + "at time of clearing"); + + " at time of clearing"); mWakeLockCount = 0; mWakeLock.release(); mClientWakelockTracker.stopTrackingAll(); @@ -5679,6 +6644,22 @@ public class RIL extends BaseCommands implements CommandsInterface { sb.append("[").append(hwcfg).append("] "); } s = sb.toString(); + } else if (req == RIL_REQUEST_START_IMS_TRAFFIC + || req == RIL_UNSOL_CONNECTION_SETUP_FAILURE) { + sb = new StringBuilder("{"); + Object[] info = (Object[]) ret; + int token = (Integer) info[0]; + sb.append(token).append(", "); + if (info[1] != null) { + ConnectionFailureInfo failureInfo = (ConnectionFailureInfo) info[1]; + sb.append(failureInfo.getReason()).append(", "); + sb.append(failureInfo.getCauseCode()).append(", "); + sb.append(failureInfo.getWaitTimeMillis()); + } else { + sb.append("null"); + } + sb.append("}"); + s = sb.toString(); } else { // Check if toString() was overridden. Java classes created from HIDL have a built-in // toString() method, but AIDL classes only have it if the parcelable contains a @@ -5856,6 +6837,13 @@ public class RIL extends BaseCommands implements CommandsInterface { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("RIL: " + this); + pw.println(" " + mServiceProxies.get(HAL_SERVICE_DATA)); + pw.println(" " + mServiceProxies.get(HAL_SERVICE_MESSAGING)); + pw.println(" " + mServiceProxies.get(HAL_SERVICE_MODEM)); + pw.println(" " + mServiceProxies.get(HAL_SERVICE_NETWORK)); + pw.println(" " + mServiceProxies.get(HAL_SERVICE_SIM)); + pw.println(" " + mServiceProxies.get(HAL_SERVICE_VOICE)); + pw.println(" " + mServiceProxies.get(HAL_SERVICE_IMS)); pw.println(" mWakeLock=" + mWakeLock); pw.println(" mWakeLockTimeout=" + mWakeLockTimeout); synchronized (mRequestList) { @@ -5931,31 +6919,60 @@ public class RIL extends BaseCommands implements CommandsInterface { new CellSignalStrengthNr()); } + void notifyBarringInfoChanged(@NonNull BarringInfo barringInfo) { + mLastBarringInfo = barringInfo; + mBarringInfoChangedRegistrants.notifyRegistrants(new AsyncResult(null, barringInfo, null)); + } + /** - * Get the HAL version. + * Get the HAL version with a specific service. * + * @param service the hal service id * @return the current HalVersion */ - public HalVersion getHalVersion() { - return mRadioVersion; + public HalVersion getHalVersion(int service) { + HalVersion halVersion = mHalVersion.get(service); + if (halVersion == null) { + if (isRadioServiceSupported(service)) { + halVersion = RADIO_HAL_VERSION_UNKNOWN; + } else { + halVersion = RADIO_HAL_VERSION_UNSUPPORTED; + } + } + return halVersion; + } + + /** + * Get the HAL version corresponding to the interface version of a IRadioService module. + * @param interfaceVersion The interface version, from IRadioService#getInterfaceVersion(). + * @return The corresponding HalVersion. + */ + public static HalVersion getServiceHalVersion(int interfaceVersion) { + switch (interfaceVersion) { + case 1: return RADIO_HAL_VERSION_2_0; + case 2: return RADIO_HAL_VERSION_2_1; + default: return RADIO_HAL_VERSION_UNKNOWN; + } } - private static String serviceToString(int service) { + private static String serviceToString(@HalService int service) { switch (service) { - case RADIO_SERVICE: + case HAL_SERVICE_RADIO: return "RADIO"; - case DATA_SERVICE: + case HAL_SERVICE_DATA: return "DATA"; - case MESSAGING_SERVICE: + case HAL_SERVICE_MESSAGING: return "MESSAGING"; - case MODEM_SERVICE: + case HAL_SERVICE_MODEM: return "MODEM"; - case NETWORK_SERVICE: + case HAL_SERVICE_NETWORK: return "NETWORK"; - case SIM_SERVICE: + case HAL_SERVICE_SIM: return "SIM"; - case VOICE_SERVICE: + case HAL_SERVICE_VOICE: return "VOICE"; + case HAL_SERVICE_IMS: + return "IMS"; default: return "UNKNOWN:" + service; } diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java index 61f1e82a769730ac62da42669ed2853df44616de..9db186fbf886999ffb1f5e884f6444e31b89addd 100644 --- a/src/java/com/android/internal/telephony/RILUtils.java +++ b/src/java/com/android/internal/telephony/RILUtils.java @@ -29,6 +29,7 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ALLOCATE_P import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ALLOW_DATA; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ANSWER; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_BASEBAND_VERSION; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CANCEL_HANDOVER; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CANCEL_USSD; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_BROADCAST_ACTIVATION; @@ -58,6 +59,7 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DATA_REGIS import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEACTIVATE_DATA_CALL; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DELETE_SMS_ON_SIM; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IDENTITY; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IMEI; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DIAL; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF_START; @@ -74,6 +76,7 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PUK; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PUK2; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EXIT_EMERGENCY_MODE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EXPLICIT_CALL_TRANSFER; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_ACTIVITY_INFO; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_ALLOWED_CARRIERS; @@ -112,7 +115,9 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_HANGUP_WAI import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IMS_REGISTRATION_STATE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IMS_SEND_SMS; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ISIM_AUTHENTICATION; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_N1_MODE_ENABLED; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_VONR_ENABLED; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_LAST_CALL_FAIL_CAUSE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE; @@ -138,6 +143,7 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_REPORT_SMS import static com.android.internal.telephony.RILConstants.RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RESET_RADIO; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SCREEN_STATE; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_ANBR_QUERY; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_DEVICE_STATE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS_EXPECT_MORE; @@ -154,20 +160,24 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CLIR; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_PROFILE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_THROTTLING; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DC_RT_INFO_RATE; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_EMERGENCY_MODE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_FACILITY_LOCK; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_INITIAL_ATTACH_APN; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOCATION_UPDATES; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_MUTE; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_N1_MODE_ENABLED; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_RADIO_CAPABILITY; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SIM_CARD_POWER; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SMSC_ADDRESS; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SRVCC_CALL_INFO; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_TTY_MODE; @@ -185,6 +195,7 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_TRANSM import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SMS_ACKNOWLEDGE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_HANDOVER; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_IMS_TRAFFIC; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_KEEPALIVE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_LCE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_NETWORK_SCAN; @@ -194,12 +205,17 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SEND_E import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SET_PROFILE; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_IMS_TRAFFIC; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_KEEPALIVE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_LCE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_NETWORK_SCAN; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_TRIGGER_EPS_FALLBACK; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UDUB; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UPDATE_IMS_CALL_STATUS; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_VOICE_RADIO_TECH; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_VOICE_REGISTRATION_STATE; @@ -214,8 +230,10 @@ import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_PRL_CHA import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELL_INFO_LIST; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CONNECTION_SETUP_FAILURE; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_DATA_CALL_LIST_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_DC_RT_INFO_CHANGED; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EMERGENCY_NUMBER_LIST; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE; @@ -226,6 +244,7 @@ import static com.android.internal.telephony.RILConstants.RIL_UNSOL_LCEDATA_RECV import static com.android.internal.telephony.RILConstants.RIL_UNSOL_MODEM_RESTART; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NETWORK_SCAN_RESULT; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NITZ_TIME_RECEIVED; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NOTIFY_ANBR; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_OEM_HOOK_RAW; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_SS; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_USSD; @@ -253,6 +272,7 @@ import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RINGBACK_TON import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIGNAL_STRENGTH; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIM_REFRESH; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIM_SMS_STORAGE_FULL; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SLICING_CONFIG_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SRVCC_STATE_NOTIFY; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_CALL_SETUP; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_CC_ALPHA_NOTIFY; @@ -260,11 +280,13 @@ import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_EVENT_NO import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_PROACTIVE_COMMAND; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_SESSION_END; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SUPP_SVC_NOTIFICATION; +import static com.android.internal.telephony.RILConstants.RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UNTHROTTLE_APN; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_TECH_CHANGED; +import android.annotation.NonNull; import android.annotation.Nullable; import android.net.InetAddresses; import android.net.LinkAddress; @@ -298,8 +320,11 @@ import android.telephony.CellSignalStrengthNr; import android.telephony.CellSignalStrengthTdscdma; import android.telephony.CellSignalStrengthWcdma; import android.telephony.ClosedSubscriberGroupInfo; +import android.telephony.DomainSelectionService; +import android.telephony.EmergencyRegResult; import android.telephony.LinkCapacityEstimate; import android.telephony.ModemInfo; +import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneCapability; import android.telephony.PhoneNumberUtils; import android.telephony.PhysicalChannelConfig; @@ -326,6 +351,12 @@ import android.telephony.data.QosBearerSession; import android.telephony.data.RouteSelectionDescriptor; import android.telephony.data.TrafficDescriptor; import android.telephony.data.UrspRule; +import android.telephony.ims.RegistrationManager; +import android.telephony.ims.feature.ConnectionFailureInfo; +import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase.ImsDeregistrationReason; +import android.telephony.satellite.SatelliteManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.SparseArray; @@ -339,6 +370,7 @@ import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress; import com.android.internal.telephony.cdma.sms.SmsEnvelope; import com.android.internal.telephony.data.KeepaliveStatus; import com.android.internal.telephony.data.KeepaliveStatus.KeepaliveStatusCode; +import com.android.internal.telephony.imsphone.ImsCallInfo; import com.android.internal.telephony.uicc.AdnCapacity; import com.android.internal.telephony.uicc.IccCardApplicationStatus; import com.android.internal.telephony.uicc.IccCardStatus; @@ -346,6 +378,7 @@ import com.android.internal.telephony.uicc.IccSimPortInfo; import com.android.internal.telephony.uicc.IccSlotPortMapping; import com.android.internal.telephony.uicc.IccSlotStatus; import com.android.internal.telephony.uicc.IccUtils; +import com.android.internal.telephony.uicc.PortUtils; import com.android.internal.telephony.uicc.SimPhonebookRecord; import com.android.telephony.Rlog; @@ -1441,8 +1474,9 @@ public class RILUtils { if (ComprehensionTlvTag.TEXT_STRING.value() == ctlv.getTag()) { byte[] target = Arrays.copyOfRange(ctlv.getRawValue(), from, ctlv.getValueIndex() + ctlv.getLength()); - terminalResponse = terminalResponse.toLowerCase().replace( - IccUtils.bytesToHexString(target).toLowerCase(), "********"); + terminalResponse = terminalResponse.toLowerCase(Locale.ROOT).replace( + IccUtils.bytesToHexString(target).toLowerCase(Locale.ROOT), + "********"); } // The text string tag and the length field should also be hidden. from = ctlv.getValueIndex() + ctlv.getLength(); @@ -1660,12 +1694,10 @@ public class RILUtils { if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN) != 0) { raf |= android.hardware.radio.RadioAccessFamily.IWLAN; } - if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE) != 0) { + if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE) != 0 + || (networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA) != 0) { raf |= android.hardware.radio.RadioAccessFamily.LTE; } - if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA) != 0) { - raf |= android.hardware.radio.RadioAccessFamily.LTE_CA; - } if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0) { raf |= android.hardware.radio.RadioAccessFamily.NR; } @@ -1801,10 +1833,12 @@ public class RILUtils { * @param p2 p2 * @param p3 p3 * @param data data + * @param radioHalVersion radio hal version * @return The converted SimApdu */ public static android.hardware.radio.sim.SimApdu convertToHalSimApduAidl(int channel, int cla, - int instruction, int p1, int p2, int p3, String data) { + int instruction, int p1, int p2, int p3, String data, boolean isEs10Command, + HalVersion radioHalVersion) { android.hardware.radio.sim.SimApdu msg = new android.hardware.radio.sim.SimApdu(); msg.sessionId = channel; msg.cla = cla; @@ -1813,6 +1847,9 @@ public class RILUtils { msg.p2 = p2; msg.p3 = p3; msg.data = convertNullToEmptyString(data); + if (radioHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_1)) { + msg.isEs10 = isEs10Command; + } return msg; } @@ -3256,7 +3293,8 @@ public class RILUtils { return new CellSignalStrengthNr(CellSignalStrengthNr.flip(ss.base.csiRsrp), CellSignalStrengthNr.flip(ss.base.csiRsrq), ss.base.csiSinr, ss.csiCqiTableIndex, ss.csiCqiReport, CellSignalStrengthNr.flip(ss.base.ssRsrp), - CellSignalStrengthNr.flip(ss.base.ssRsrq), ss.base.ssSinr); + CellSignalStrengthNr.flip(ss.base.ssRsrq), ss.base.ssSinr, + CellInfo.UNAVAILABLE); } return null; } @@ -3271,7 +3309,7 @@ public class RILUtils { return new CellSignalStrengthNr(CellSignalStrengthNr.flip(ss.csiRsrp), CellSignalStrengthNr.flip(ss.csiRsrq), ss.csiSinr, ss.csiCqiTableIndex, primitiveArrayToArrayList(ss.csiCqiReport), CellSignalStrengthNr.flip(ss.ssRsrp), - CellSignalStrengthNr.flip(ss.ssRsrq), ss.ssSinr); + CellSignalStrengthNr.flip(ss.ssRsrq), ss.ssSinr, ss.timingAdvance); } private static ClosedSubscriberGroupInfo convertHalClosedSubscriberGroupInfo( @@ -3835,9 +3873,13 @@ public class RILUtils { convertHalQosBandwidth(eps.uplink), eps.qci); case android.hardware.radio.data.Qos.nr: android.hardware.radio.data.NrQos nr = qos.getNr(); + int averagingWindowMs = nr.averagingWindowMillis; + if (averagingWindowMs + == android.hardware.radio.data.NrQos.AVERAGING_WINDOW_UNKNOWN) { + averagingWindowMs = nr.averagingWindowMs; + } return new NrQos(convertHalQosBandwidth(nr.downlink), - convertHalQosBandwidth(nr.uplink), nr.qfi, nr.fiveQi, - nr.averagingWindowMs); + convertHalQosBandwidth(nr.uplink), nr.qfi, nr.fiveQi, averagingWindowMs); default: return null; } @@ -4331,6 +4373,7 @@ public class RILUtils { android.hardware.radio.sim.CardStatus cardStatus) { IccCardStatus iccCardStatus = new IccCardStatus(); iccCardStatus.setCardState(cardStatus.cardState); + iccCardStatus.setMultipleEnabledProfilesMode(cardStatus.supportedMepMode); iccCardStatus.setUniversalPinState(cardStatus.universalPinState); iccCardStatus.mGsmUmtsSubscriptionAppIndex = cardStatus.gsmUmtsSubscriptionAppIndex; iccCardStatus.mCdmaSubscriptionAppIndex = cardStatus.cdmaSubscriptionAppIndex; @@ -4358,7 +4401,9 @@ public class RILUtils { } IccSlotPortMapping slotPortMapping = new IccSlotPortMapping(); slotPortMapping.mPhysicalSlotIndex = cardStatus.slotMap.physicalSlotId; - slotPortMapping.mPortIndex = cardStatus.slotMap.portId; + slotPortMapping.mPortIndex = PortUtils.convertFromHalPortIndex( + cardStatus.slotMap.physicalSlotId, cardStatus.slotMap.portId, + iccCardStatus.mCardState, iccCardStatus.mSupportedMepMode); iccCardStatus.mSlotPortMapping = slotPortMapping; return iccCardStatus; } @@ -4474,6 +4519,7 @@ public class RILUtils { } iccSlotStatus.atr = slotStatus.atr; iccSlotStatus.eid = slotStatus.eid; + iccSlotStatus.setMultipleEnabledProfilesMode(slotStatus.supportedMepMode); response.add(iccSlotStatus); } return response; @@ -4541,7 +4587,8 @@ public class RILUtils { int logicalSlotIdx = mapping.getLogicalSlotIndex(); res[logicalSlotIdx] = new android.hardware.radio.config.SlotPortMapping(); res[logicalSlotIdx].physicalSlotId = mapping.getPhysicalSlotIndex(); - res[logicalSlotIdx].portId = mapping.getPortIndex(); + res[logicalSlotIdx].portId = PortUtils.convertToHalPortIndex( + mapping.getPhysicalSlotIndex(), mapping.getPortIndex()); } return res; } @@ -4566,12 +4613,14 @@ public class RILUtils { public static PhoneCapability convertHalPhoneCapability(int[] deviceNrCapabilities, Object o) { int maxActiveVoiceCalls = 0; int maxActiveData = 0; + int maxActiveInternetData = 0; boolean validationBeforeSwitchSupported = false; List logicalModemList = new ArrayList<>(); if (o instanceof android.hardware.radio.config.PhoneCapability) { final android.hardware.radio.config.PhoneCapability phoneCapability = (android.hardware.radio.config.PhoneCapability) o; maxActiveData = phoneCapability.maxActiveData; + maxActiveInternetData = phoneCapability.maxActiveInternetData; validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported; for (int modemId : phoneCapability.logicalModemIds) { logicalModemList.add(new ModemInfo(modemId)); @@ -4580,16 +4629,249 @@ public class RILUtils { final android.hardware.radio.config.V1_1.PhoneCapability phoneCapability = (android.hardware.radio.config.V1_1.PhoneCapability) o; maxActiveData = phoneCapability.maxActiveData; + maxActiveInternetData = phoneCapability.maxActiveInternetData; validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported; for (android.hardware.radio.config.V1_1.ModemInfo modemInfo : phoneCapability.logicalModemList) { logicalModemList.add(new ModemInfo(modemInfo.modemId)); } } + // maxActiveInternetData defines how many logical modems can have internet PDN connections + // simultaneously. For L+L DSDS modem it’s 1, and for DSDA modem it’s 2. + maxActiveVoiceCalls = maxActiveInternetData; return new PhoneCapability(maxActiveVoiceCalls, maxActiveData, logicalModemList, validationBeforeSwitchSupported, deviceNrCapabilities); } + /** + * Convert network scan type + * @param scanType The network scan type + * @return The converted EmergencyScanType + */ + public static int convertEmergencyScanType(int scanType) { + switch (scanType) { + case DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE: + return android.hardware.radio.network.EmergencyScanType.LIMITED_SERVICE; + case DomainSelectionService.SCAN_TYPE_FULL_SERVICE: + return android.hardware.radio.network.EmergencyScanType.FULL_SERVICE; + default: + return android.hardware.radio.network.EmergencyScanType.NO_PREFERENCE; + } + } + + /** + * Convert to EmergencyNetworkScanTrigger + * @param accessNetwork The list of access network types + * @param scanType The network scan type + * @return The converted EmergencyNetworkScanTrigger + */ + public static android.hardware.radio.network.EmergencyNetworkScanTrigger + convertEmergencyNetworkScanTrigger(@NonNull int[] accessNetwork, int scanType) { + int[] halAccessNetwork = new int[accessNetwork.length]; + for (int i = 0; i < accessNetwork.length; i++) { + halAccessNetwork[i] = convertToHalAccessNetworkAidl(accessNetwork[i]); + } + + android.hardware.radio.network.EmergencyNetworkScanTrigger scanRequest = + new android.hardware.radio.network.EmergencyNetworkScanTrigger(); + + scanRequest.accessNetwork = halAccessNetwork; + scanRequest.scanType = convertEmergencyScanType(scanType); + return scanRequest; + } + + /** + * Convert EmergencyRegResult.aidl to EmergencyRegResult. + * @param halResult EmergencyRegResult.aidl in HAL. + * @return Converted EmergencyRegResult. + */ + public static EmergencyRegResult convertHalEmergencyRegResult( + android.hardware.radio.network.EmergencyRegResult halResult) { + return new EmergencyRegResult( + halResult.accessNetwork, + convertHalRegState(halResult.regState), + halResult.emcDomain, + halResult.isVopsSupported, + halResult.isEmcBearerSupported, + halResult.nwProvidedEmc, + halResult.nwProvidedEmf, + halResult.mcc, + halResult.mnc, + getCountryCodeForMccMnc(halResult.mcc, halResult.mnc)); + } + + private static @NonNull String getCountryCodeForMccMnc( + @NonNull String mcc, @NonNull String mnc) { + if (TextUtils.isEmpty(mcc)) return ""; + if (TextUtils.isEmpty(mnc)) mnc = "000"; + String operatorNumeric = TextUtils.concat(mcc, mnc).toString(); + + MccTable.MccMnc mccMnc = MccTable.MccMnc.fromOperatorNumeric(operatorNumeric); + return MccTable.geoCountryCodeForMccMnc(mccMnc); + } + + /** + * Convert RegResult.aidl to RegistrationState. + * @param halRegState RegResult in HAL. + * @return Converted RegistrationState. + */ + public static @NetworkRegistrationInfo.RegistrationState int convertHalRegState( + int halRegState) { + switch (halRegState) { + case android.hardware.radio.network.RegState.NOT_REG_MT_NOT_SEARCHING_OP: + case android.hardware.radio.network.RegState.NOT_REG_MT_NOT_SEARCHING_OP_EM: + return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + case android.hardware.radio.network.RegState.REG_HOME: + return NetworkRegistrationInfo.REGISTRATION_STATE_HOME; + case android.hardware.radio.network.RegState.NOT_REG_MT_SEARCHING_OP: + case android.hardware.radio.network.RegState.NOT_REG_MT_SEARCHING_OP_EM: + return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING; + case android.hardware.radio.network.RegState.REG_DENIED: + case android.hardware.radio.network.RegState.REG_DENIED_EM: + return NetworkRegistrationInfo.REGISTRATION_STATE_DENIED; + case android.hardware.radio.network.RegState.UNKNOWN: + case android.hardware.radio.network.RegState.UNKNOWN_EM: + return NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN; + case android.hardware.radio.network.RegState.REG_ROAMING: + return NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; + default: + return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + } + } + + /** Converts the array of network types to readable String array */ + public static @NonNull String accessNetworkTypesToString( + @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetworkTypes) { + int length = accessNetworkTypes.length; + StringBuilder sb = new StringBuilder("{"); + if (length > 0) { + sb.append(Arrays.stream(accessNetworkTypes) + .mapToObj(RILUtils::accessNetworkTypeToString) + .collect(Collectors.joining(","))); + } + sb.append("}"); + return sb.toString(); + } + + private static @NonNull String accessNetworkTypeToString( + @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType) { + switch (accessNetworkType) { + case AccessNetworkConstants.AccessNetworkType.UNKNOWN: return "UNKNOWN"; + case AccessNetworkConstants.AccessNetworkType.GERAN: return "GERAN"; + case AccessNetworkConstants.AccessNetworkType.UTRAN: return "UTRAN"; + case AccessNetworkConstants.AccessNetworkType.EUTRAN: return "EUTRAN"; + case AccessNetworkConstants.AccessNetworkType.CDMA2000: return "CDMA2000"; + case AccessNetworkConstants.AccessNetworkType.IWLAN: return "IWLAN"; + case AccessNetworkConstants.AccessNetworkType.NGRAN: return "NGRAN"; + default: return Integer.toString(accessNetworkType); + } + } + + /** Converts scan type to readable String */ + public static @NonNull String scanTypeToString( + @DomainSelectionService.EmergencyScanType int scanType) { + switch (scanType) { + case DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE: + return "LIMITED_SERVICE"; + case DomainSelectionService.SCAN_TYPE_FULL_SERVICE: + return "FULL_SERVICE"; + default: + return "NO_PREFERENCE"; + } + } + + /** Convert IMS deregistration reason */ + public static @ImsDeregistrationReason int convertHalDeregistrationReason(int reason) { + switch (reason) { + case android.hardware.radio.ims.ImsDeregistrationReason.REASON_SIM_REMOVED: + return ImsRegistrationImplBase.REASON_SIM_REMOVED; + case android.hardware.radio.ims.ImsDeregistrationReason.REASON_SIM_REFRESH: + return ImsRegistrationImplBase.REASON_SIM_REFRESH; + case android.hardware.radio.ims.ImsDeregistrationReason + .REASON_ALLOWED_NETWORK_TYPES_CHANGED: + return ImsRegistrationImplBase.REASON_ALLOWED_NETWORK_TYPES_CHANGED; + default: + return ImsRegistrationImplBase.REASON_UNKNOWN; + } + } + + /** + * Convert the IMS traffic type. + * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc. + * @return The converted IMS traffic type. + */ + public static int convertImsTrafficType(@MmTelFeature.ImsTrafficType int trafficType) { + switch (trafficType) { + case MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY: + return android.hardware.radio.ims.ImsTrafficType.EMERGENCY; + case MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS: + return android.hardware.radio.ims.ImsTrafficType.EMERGENCY_SMS; + case MmTelFeature.IMS_TRAFFIC_TYPE_VOICE: + return android.hardware.radio.ims.ImsTrafficType.VOICE; + case MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO: + return android.hardware.radio.ims.ImsTrafficType.VIDEO; + case MmTelFeature.IMS_TRAFFIC_TYPE_SMS: + return android.hardware.radio.ims.ImsTrafficType.SMS; + case MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION: + return android.hardware.radio.ims.ImsTrafficType.REGISTRATION; + } + return android.hardware.radio.ims.ImsTrafficType.UT_XCAP; + } + + /** + * Convert the IMS traffic direction. + * @param trafficDirection Indicates the traffic direction. + * @return The converted IMS traffic direction. + */ + public static int convertImsTrafficDirection( + @MmTelFeature.ImsTrafficDirection int trafficDirection) { + switch (trafficDirection) { + case MmTelFeature.IMS_TRAFFIC_DIRECTION_INCOMING: + return android.hardware.radio.ims.ImsCall.Direction.INCOMING; + default: + return android.hardware.radio.ims.ImsCall.Direction.OUTGOING; + } + } + + /** + * Convert the IMS connection failure reason. + * @param halReason Specifies the reason that IMS connection failed. + * @return The converted IMS connection failure reason. + */ + public static @ConnectionFailureInfo.FailureReason int convertHalConnectionFailureReason( + int halReason) { + switch (halReason) { + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_ACCESS_DENIED: + return ConnectionFailureInfo.REASON_ACCESS_DENIED; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_NAS_FAILURE: + return ConnectionFailureInfo.REASON_NAS_FAILURE; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_RACH_FAILURE: + return ConnectionFailureInfo.REASON_RACH_FAILURE; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_RLC_FAILURE: + return ConnectionFailureInfo.REASON_RLC_FAILURE; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_RRC_REJECT: + return ConnectionFailureInfo.REASON_RRC_REJECT; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_RRC_TIMEOUT: + return ConnectionFailureInfo.REASON_RRC_TIMEOUT; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_NO_SERVICE: + return ConnectionFailureInfo.REASON_NO_SERVICE; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_PDN_NOT_AVAILABLE: + return ConnectionFailureInfo.REASON_PDN_NOT_AVAILABLE; + case android.hardware.radio.ims.ConnectionFailureInfo + .ConnectionFailureReason.REASON_RF_BUSY: + return ConnectionFailureInfo.REASON_RF_BUSY; + } + return ConnectionFailureInfo.REASON_UNSPECIFIED; + } + /** Append the data to the end of an ArrayList */ public static void appendPrimitiveArrayToArrayList(byte[] src, ArrayList dst) { for (byte b : src) { @@ -4983,6 +5265,9 @@ public class RILUtils { return "GET_SIM_PHONEBOOK_RECORDS"; case RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD: return "UPDATE_SIM_PHONEBOOK_RECORD"; + case RIL_REQUEST_DEVICE_IMEI: + return "DEVICE_IMEI"; + /* The following requests are not defined in RIL.h */ case RIL_REQUEST_GET_SLOT_STATUS: return "GET_SLOT_STATUS"; case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING: @@ -5041,6 +5326,36 @@ public class RILUtils { return "SET_USAGE_SETTING"; case RIL_REQUEST_GET_USAGE_SETTING: return "GET_USAGE_SETTING"; + case RIL_REQUEST_SET_EMERGENCY_MODE: + return "SET_EMERGENCY_MODE"; + case RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN: + return "TRIGGER_EMERGENCY_NETWORK_SCAN"; + case RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN: + return "CANCEL_EMERGENCY_NETWORK_SCAN"; + case RIL_REQUEST_EXIT_EMERGENCY_MODE: + return "EXIT_EMERGENCY_MODE"; + case RIL_REQUEST_SET_SRVCC_CALL_INFO: + return "SET_SRVCC_CALL_INFO"; + case RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO: + return "UPDATE_IMS_REGISTRATION_INFO"; + case RIL_REQUEST_START_IMS_TRAFFIC: + return "START_IMS_TRAFFIC"; + case RIL_REQUEST_STOP_IMS_TRAFFIC: + return "STOP_IMS_TRAFFIC"; + case RIL_REQUEST_SEND_ANBR_QUERY: + return "SEND_ANBR_QUERY"; + case RIL_REQUEST_TRIGGER_EPS_FALLBACK: + return "TRIGGER_EPS_FALLBACK"; + case RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED: + return "SET_NULL_CIPHER_AND_INTEGRITY_ENABLED"; + case RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED: + return "IS_NULL_CIPHER_AND_INTEGRITY_ENABLED"; + case RIL_REQUEST_UPDATE_IMS_CALL_STATUS: + return "UPDATE_IMS_CALL_STATUS"; + case RIL_REQUEST_SET_N1_MODE_ENABLED: + return "SET_N1_MODE_ENABLED"; + case RIL_REQUEST_IS_N1_MODE_ENABLED: + return "IS_N1_MODE_ENABLED"; default: return ""; } @@ -5161,6 +5476,9 @@ public class RILUtils { return "UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED"; case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED: return "UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED"; + case RIL_UNSOL_SLICING_CONFIG_CHANGED: + return "UNSOL_SLICING_CONFIG_CHANGED"; + /* The follow unsols are not defined in RIL.h */ case RIL_UNSOL_ICC_SLOT_STATUS: return "UNSOL_ICC_SLOT_STATUS"; case RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG: @@ -5173,8 +5491,16 @@ public class RILUtils { return "UNSOL_REGISTRATION_FAILED"; case RIL_UNSOL_BARRING_INFO_CHANGED: return "UNSOL_BARRING_INFO_CHANGED"; + case RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT: + return "UNSOL_EMERGENCY_NETWORK_SCAN_RESULT"; + case RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION: + return "UNSOL_TRIGGER_IMS_DEREGISTRATION"; + case RIL_UNSOL_CONNECTION_SETUP_FAILURE: + return "UNSOL_CONNECTION_SETUP_FAILURE"; + case RIL_UNSOL_NOTIFY_ANBR: + return "UNSOL_NOTIFY_ANBR"; default: - return ""; + return ""; } } @@ -5321,6 +5647,302 @@ public class RILUtils { return sb.toString(); } + /** + * Converts the list of call information for Single Radio Voice Call Continuity(SRVCC). + * + * @param srvccConnections The list of call information for SRVCC. + * @return The converted list of call information. + */ + public static android.hardware.radio.ims.SrvccCall[] convertToHalSrvccCall( + SrvccConnection[] srvccConnections) { + if (srvccConnections == null) { + return new android.hardware.radio.ims.SrvccCall[0]; + } + + int length = srvccConnections.length; + android.hardware.radio.ims.SrvccCall[] srvccCalls = + new android.hardware.radio.ims.SrvccCall[length]; + + for (int i = 0; i < length; i++) { + srvccCalls[i] = new android.hardware.radio.ims.SrvccCall(); + srvccCalls[i].index = i + 1; + srvccCalls[i].callType = convertSrvccCallType(srvccConnections[i].getType()); + srvccCalls[i].callState = convertCallState(srvccConnections[i].getState()); + srvccCalls[i].callSubstate = + convertSrvccCallSubState(srvccConnections[i].getSubState()); + srvccCalls[i].ringbackToneType = + convertSrvccCallRingbackToneType(srvccConnections[i].getRingbackToneType()); + srvccCalls[i].isMpty = srvccConnections[i].isMultiParty(); + srvccCalls[i].isMT = srvccConnections[i].isIncoming(); + srvccCalls[i].number = TextUtils.emptyIfNull(srvccConnections[i].getNumber()); + srvccCalls[i].numPresentation = + convertPresentation(srvccConnections[i].getNumberPresentation()); + srvccCalls[i].name = TextUtils.emptyIfNull(srvccConnections[i].getName()); + srvccCalls[i].namePresentation = + convertPresentation(srvccConnections[i].getNamePresentation()); + } + + return srvccCalls; + } + + /** + * Converts the call type. + * + * @param type The call type. + * @return The converted call type. + */ + public static int convertSrvccCallType(int type) { + switch (type) { + case SrvccConnection.CALL_TYPE_NORMAL: + return android.hardware.radio.ims.SrvccCall.CallType.NORMAL; + case SrvccConnection.CALL_TYPE_EMERGENCY: + return android.hardware.radio.ims.SrvccCall.CallType.EMERGENCY; + default: + throw new RuntimeException("illegal call type " + type); + } + } + + /** + * Converts the call state. + * + * @param state The call state. + * @return The converted call state. + */ + public static int convertCallState(Call.State state) { + switch (state) { + case ACTIVE: return android.hardware.radio.voice.Call.STATE_ACTIVE; + case HOLDING: return android.hardware.radio.voice.Call.STATE_HOLDING; + case DIALING: return android.hardware.radio.voice.Call.STATE_DIALING; + case ALERTING: return android.hardware.radio.voice.Call.STATE_ALERTING; + case INCOMING: return android.hardware.radio.voice.Call.STATE_INCOMING; + case WAITING: return android.hardware.radio.voice.Call.STATE_WAITING; + default: + throw new RuntimeException("illegal state " + state); + } + } + + /** + * Converts the substate of a call. + * + * @param state The substate of a call. + * @return The converted substate. + */ + public static int convertSrvccCallSubState(int state) { + switch (state) { + case SrvccConnection.SUBSTATE_NONE: + return android.hardware.radio.ims.SrvccCall.CallSubState.NONE; + case SrvccConnection.SUBSTATE_PREALERTING: + return android.hardware.radio.ims.SrvccCall.CallSubState.PREALERTING; + default: + throw new RuntimeException("illegal substate " + state); + } + } + + /** + * Converts the ringback tone type. + * + * @param type The ringback tone type. + * @return The converted ringback tone type. + */ + public static int convertSrvccCallRingbackToneType(int type) { + switch (type) { + case SrvccConnection.TONE_NONE: + return android.hardware.radio.ims.SrvccCall.ToneType.NONE; + case SrvccConnection.TONE_LOCAL: + return android.hardware.radio.ims.SrvccCall.ToneType.LOCAL; + case SrvccConnection.TONE_NETWORK: + return android.hardware.radio.ims.SrvccCall.ToneType.NETWORK; + default: + throw new RuntimeException("illegal ringback tone type " + type); + } + } + + /** + * Converts the number presentation type for caller id display. + * + * @param presentation The number presentation type. + * @return The converted presentation type. + */ + public static int convertPresentation(int presentation) { + switch (presentation) { + case PhoneConstants.PRESENTATION_ALLOWED: + return android.hardware.radio.voice.Call.PRESENTATION_ALLOWED; + case PhoneConstants.PRESENTATION_RESTRICTED: + return android.hardware.radio.voice.Call.PRESENTATION_RESTRICTED; + case PhoneConstants.PRESENTATION_UNKNOWN: + return android.hardware.radio.voice.Call.PRESENTATION_UNKNOWN; + case PhoneConstants.PRESENTATION_PAYPHONE: + return android.hardware.radio.voice.Call.PRESENTATION_PAYPHONE; + default: + throw new RuntimeException("illegal presentation " + presentation); + } + } + + /** + * Converts IMS registration state. + * + * @param state The IMS registration state. + * @return The converted HAL IMS registration state. + */ + public static int convertImsRegistrationState(int state) { + switch (state) { + case RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED: + return android.hardware.radio.ims.ImsRegistrationState.NOT_REGISTERED; + case RegistrationManager.REGISTRATION_STATE_REGISTERED: + return android.hardware.radio.ims.ImsRegistrationState.REGISTERED; + default: + throw new RuntimeException("illegal state " + state); + } + } + + /** + * Converts IMS service radio technology. + * + * @param imsRadioTech The IMS service radio technology. + * @return The converted HAL access network type. + */ + + public static int convertImsRegistrationTech( + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { + switch (imsRadioTech) { + case ImsRegistrationImplBase.REGISTRATION_TECH_LTE: + return android.hardware.radio.AccessNetwork.EUTRAN; + case ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN: + return android.hardware.radio.AccessNetwork.IWLAN; + case ImsRegistrationImplBase.REGISTRATION_TECH_NR: + return android.hardware.radio.AccessNetwork.NGRAN; + case ImsRegistrationImplBase.REGISTRATION_TECH_3G: + return android.hardware.radio.AccessNetwork.UTRAN; + default: + return android.hardware.radio.AccessNetwork.UNKNOWN; + } + } + + /** + * Converts IMS capabilities. + * + * @param capabilities The IMS capabilities. + * @return The converted HAL IMS capabilities. + */ + public static int convertImsCapability(int capabilities) { + int halCapabilities = android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_NONE; + if ((capabilities & CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE) > 0) { + halCapabilities |= + android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_VOICE; + } + if ((capabilities & CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO) > 0) { + halCapabilities |= + android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_VIDEO; + } + if ((capabilities & CommandsInterface.IMS_MMTEL_CAPABILITY_SMS) > 0) { + halCapabilities |= android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_SMS; + } + if ((capabilities & CommandsInterface.IMS_RCS_CAPABILITIES) > 0) { + halCapabilities |= android.hardware.radio.ims.ImsRegistration.IMS_RCS_CAPABILITIES; + } + return halCapabilities; + } + + /** Converts the ImsCallInfo instances to HAL ImsCall instances. */ + public static android.hardware.radio.ims.ImsCall[] convertImsCallInfo( + List imsCallInfos) { + if (imsCallInfos == null) { + return new android.hardware.radio.ims.ImsCall[0]; + } + + int length = 0; + for (int i = 0; i < imsCallInfos.size(); i++) { + if (imsCallInfos.get(i) != null) length++; + } + if (length == 0) { + return new android.hardware.radio.ims.ImsCall[0]; + } + + android.hardware.radio.ims.ImsCall[] halInfos = + new android.hardware.radio.ims.ImsCall[length]; + + int index = 0; + for (int i = 0; i < imsCallInfos.size(); i++) { + ImsCallInfo info = imsCallInfos.get(i); + if (info == null) continue; + + halInfos[index] = new android.hardware.radio.ims.ImsCall(); + halInfos[index].index = info.getIndex(); + halInfos[index].callState = convertToHalImsCallState(info.getCallState()); + halInfos[index].callType = info.isEmergencyCall() + ? android.hardware.radio.ims.ImsCall.CallType.EMERGENCY + : android.hardware.radio.ims.ImsCall.CallType.NORMAL; + halInfos[index].accessNetwork = convertToHalAccessNetworkAidl(info.getCallRadioTech()); + halInfos[index].direction = info.isIncoming() + ? android.hardware.radio.ims.ImsCall.Direction.INCOMING + : android.hardware.radio.ims.ImsCall.Direction.OUTGOING; + halInfos[index].isHeldByRemote = info.isHeldByRemote(); + index++; + } + + return halInfos; + } + + /** + * Convert satellite-related errors from CommandException.Error to + * SatelliteManager.SatelliteServiceResult. + * @param error The satellite error. + * @return The converted SatelliteServiceResult. + */ + @SatelliteManager.SatelliteError + public static int convertToSatelliteError( + CommandException.Error error) { + switch (error) { + case INTERNAL_ERR: + //fallthrough to SYSTEM_ERR + case MODEM_ERR: + //fallthrough to SYSTEM_ERR + case SYSTEM_ERR: + return SatelliteManager.SATELLITE_MODEM_ERROR; + case INVALID_ARGUMENTS: + return SatelliteManager.SATELLITE_INVALID_ARGUMENTS; + case INVALID_MODEM_STATE: + return SatelliteManager.SATELLITE_INVALID_MODEM_STATE; + case RADIO_NOT_AVAILABLE: + return SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE; + case REQUEST_NOT_SUPPORTED: + return SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED; + case NO_MEMORY: + //fallthrough to NO_RESOURCES + case NO_RESOURCES: + return SatelliteManager.SATELLITE_NO_RESOURCES; + case NETWORK_ERR: + return SatelliteManager.SATELLITE_NETWORK_ERROR; + case NO_NETWORK_FOUND: + return SatelliteManager.SATELLITE_NOT_REACHABLE; + case ABORTED: + return SatelliteManager.SATELLITE_REQUEST_ABORTED; + case ACCESS_BARRED: + return SatelliteManager.SATELLITE_ACCESS_BARRED; + default: + return SatelliteManager.SATELLITE_ERROR; + } + } + + /** + * Converts the call state to HAL IMS call state. + * + * @param state The {@link Call.State}. + * @return The converted {@link android.hardware.radio.ims.ImsCall.CallState}. + */ + private static int convertToHalImsCallState(Call.State state) { + switch (state) { + case ACTIVE: return android.hardware.radio.ims.ImsCall.CallState.ACTIVE; + case HOLDING: return android.hardware.radio.ims.ImsCall.CallState.HOLDING; + case DIALING: return android.hardware.radio.ims.ImsCall.CallState.DIALING; + case ALERTING: return android.hardware.radio.ims.ImsCall.CallState.ALERTING; + case INCOMING: return android.hardware.radio.ims.ImsCall.CallState.INCOMING; + case WAITING: return android.hardware.radio.ims.ImsCall.CallState.WAITING; + case DISCONNECTING: return android.hardware.radio.ims.ImsCall.CallState.DISCONNECTING; + default: return android.hardware.radio.ims.ImsCall.CallState.DISCONNECTED; + } + } + private static void logd(String log) { Rlog.d("RILUtils", log); } diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java index e455ecbe7419d3f266d3fe0c1cc08930c93c360a..3e2be1d6ae334327cd28e8345331b975fdd7cf9d 100644 --- a/src/java/com/android/internal/telephony/RadioConfig.java +++ b/src/java/com/android/internal/telephony/RadioConfig.java @@ -358,7 +358,7 @@ public class RadioConfig extends Handler { if (rr != null) { Trace.asyncTraceForTrackEnd( - Trace.TRACE_TAG_NETWORK, "RIL", "" /* unused */, rr.mSerial); + Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial); mRequestList.remove(serial); } } @@ -642,4 +642,9 @@ public class RadioConfig extends Handler { private static void loge(String log) { Rlog.e(TAG, log); } + + @Override + public String toString() { + return "RadioConfig[" + "mRadioConfigProxy=" + mRadioConfigProxy + ']'; + } } diff --git a/src/java/com/android/internal/telephony/RadioConfigProxy.java b/src/java/com/android/internal/telephony/RadioConfigProxy.java index a8601f1c60567ea227b34bec88ffe7619abc35c1..edeb558b5d87ffc8762ac73e1ad94a28e923f840 100644 --- a/src/java/com/android/internal/telephony/RadioConfigProxy.java +++ b/src/java/com/android/internal/telephony/RadioConfigProxy.java @@ -336,4 +336,11 @@ public class RadioConfigProxy { mRadioConfig.obtainMessage(RadioConfig.EVENT_AIDL_SERVICE_DEAD)); } } + + @Override + public String toString() { + return "RadioConfigProxy[" + + "mRadioHalVersion=" + mRadioHalVersion + + ", mRadioConfigHalVersion=" + mRadioConfigHalVersion + ']'; + } } diff --git a/src/java/com/android/internal/telephony/RadioDataProxy.java b/src/java/com/android/internal/telephony/RadioDataProxy.java index cbc762a59d7bcf3e71b96f55f516ec8ef2db7cc8..9671077b0fa9b9c816436d13abe77eac1278b78c 100644 --- a/src/java/com/android/internal/telephony/RadioDataProxy.java +++ b/src/java/com/android/internal/telephony/RadioDataProxy.java @@ -45,12 +45,22 @@ public class RadioDataProxy extends RadioServiceProxy { * Set IRadioData as the AIDL implementation for RadioServiceProxy * @param halVersion Radio HAL version * @param data IRadioData implementation + * + * @return updated HAL version */ - public void setAidl(HalVersion halVersion, android.hardware.radio.data.IRadioData data) { - mHalVersion = halVersion; + public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.data.IRadioData data) { + HalVersion version = halVersion; + try { + version = RIL.getServiceHalVersion(data.getInterfaceVersion()); + } catch (RemoteException e) { + Rlog.e(TAG, "setAidl: " + e); + } + mHalVersion = version; mDataProxy = data; mIsAidl = true; - Rlog.d(TAG, "AIDL initialized"); + + Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion); + return mHalVersion; } /** diff --git a/src/java/com/android/internal/telephony/RadioImsProxy.java b/src/java/com/android/internal/telephony/RadioImsProxy.java new file mode 100644 index 0000000000000000000000000000000000000000..cde2e06859818ef4bd4ade8758c0bfe1311d2c47 --- /dev/null +++ b/src/java/com/android/internal/telephony/RadioImsProxy.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import android.os.RemoteException; +import android.telephony.Rlog; + +/** + * A holder for IRadioIms. + * Use getAidl to get IRadioIms and call the AIDL implementations of the HAL APIs. + */ +public class RadioImsProxy extends RadioServiceProxy { + private static final String TAG = "RadioImsProxy"; + private volatile android.hardware.radio.ims.IRadioIms mImsProxy = null; + + /** + * Sets IRadioIms as the AIDL implementation for RadioServiceProxy. + * @param halVersion Radio HAL version. + * @param ims IRadioIms implementation. + * + * @return updated HAL version. + */ + public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.ims.IRadioIms ims) { + HalVersion version = halVersion; + try { + version = RIL.getServiceHalVersion(ims.getInterfaceVersion()); + } catch (RemoteException e) { + Rlog.e(TAG, "setAidl: " + e); + } + mHalVersion = version; + mImsProxy = ims; + mIsAidl = true; + + Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion); + return mHalVersion; + } + + /** + * Gets the AIDL implementation of RadioImsProxy. + * @return IRadioIms implementation. + */ + public android.hardware.radio.ims.IRadioIms getAidl() { + return mImsProxy; + } + + /** + * Resets RadioImsProxy. + */ + @Override + public void clear() { + super.clear(); + mImsProxy = null; + } + + /** + * Checks whether a RadioIms implementation exists. + * @return true if there is neither a HIDL nor AIDL implementation. + */ + @Override + public boolean isEmpty() { + return mRadioProxy == null && mImsProxy == null; + } + + /** + * No implementation in IRadioIms. + * @throws RemoteException. + */ + @Override + public void responseAcknowledgement() throws RemoteException { + /* Currently, IRadioIms doesn't support the following response types: + * - RadioIndicationType.UNSOLICITED_ACK_EXP + * - RadioResponseType.SOLICITED_ACK_EXP */ + // no-op + } + + /** + * Calls IRadioIms#setSrvccCallInfo. + * @param serial Serial number of request. + * @param srvccCalls The list of call information. + * @throws RemoteException. + */ + public void setSrvccCallInfo(int serial, + android.hardware.radio.ims.SrvccCall[] srvccCalls) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mImsProxy.setSrvccCallInfo(serial, srvccCalls); + } + } + + /** + * Calls IRadioIms#updateImsRegistrationInfo. + * @param serial Serial number of request. + * @param registrationInfo The registration state information. + * @throws RemoteException. + */ + public void updateImsRegistrationInfo(int serial, + android.hardware.radio.ims.ImsRegistration registrationInfo) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mImsProxy.updateImsRegistrationInfo(serial, registrationInfo); + } + } + + /** + * Calls IRadioIms#startImsTraffic. + * @param serial Serial number of request. + * @param token A nonce to identify the request. + * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc. + * @param accessNetworkType The type of underlying radio access network used. + * @param trafficDirection Indicates whether traffic is originated by mobile originated or + * mobile terminated use case eg. MO/MT call/SMS etc. + * @throws RemoteException. + */ + public void startImsTraffic(int serial, int token, int trafficType, int accessNetworkType, + int trafficDirection) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mImsProxy.startImsTraffic(serial, + token, trafficType, accessNetworkType, trafficDirection); + } + } + + /** + * Calls IRadioIms#stopImsTraffic. + * @param serial Serial number of request. + * @param token The token assigned by startImsTraffic. + * @throws RemoteException. + */ + public void stopImsTraffic(int serial, int token) + throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mImsProxy.stopImsTraffic(serial, token); + } + } + + /** + * Calls IRadioIms#triggerEpsFallback. + * @param serial Serial number of request. + * @param reason Specifies the reason for EPS fallback. + * @throws RemoteException. + */ + public void triggerEpsFallback(int serial, int reason) + throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mImsProxy.triggerEpsFallback(serial, reason); + } + } + + /** + * Calls IRadioIms#sendAnbrQuery. + * @param serial Serial number of request. + * @param mediaType Media type is used to identify media stream such as audio or video. + * @param direction Direction of this packet stream (e.g. uplink or downlink). + * @param bitsPerSecond The bit rate requested by the opponent UE. + * @throws RemoteException. + */ + public void sendAnbrQuery(int serial, int mediaType, int direction, int bitsPerSecond) + throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mImsProxy.sendAnbrQuery(serial, mediaType, direction, bitsPerSecond); + } + } + + /** + * Call IRadioIms#updateImsCallStatus + * @param serial Serial number of request. + * @param imsCalls The list of call status information. + * @throws RemoteException. + */ + public void updateImsCallStatus(int serial, + android.hardware.radio.ims.ImsCall[] imsCalls) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mImsProxy.updateImsCallStatus(serial, imsCalls); + } + } +} diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java index d947d5361150e823d38eebfbc51b1b459e3b7a5e..4f75412ad91113632713c35ddf68f241111f1ca1 100644 --- a/src/java/com/android/internal/telephony/RadioIndication.java +++ b/src/java/com/android/internal/telephony/RadioIndication.java @@ -16,7 +16,7 @@ package com.android.internal.telephony; -import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; +import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CALL_RING; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION; @@ -136,7 +136,7 @@ public class RadioIndication extends IRadioIndication.Stub { * @param radioState android.hardware.radio.V1_0.RadioState */ public void radioStateChanged(int indicationType, int radioState) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int state = RILUtils.convertHalRadioState(radioState); if (mRil.isLogOrTrace()) { @@ -148,7 +148,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void callStateChanged(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED); @@ -160,7 +160,7 @@ public class RadioIndication extends IRadioIndication.Stub { * @param indicationType RadioIndicationType */ public void networkStateChanged(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED); @@ -168,7 +168,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void newSms(int indicationType, ArrayList pdu) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); byte[] pduArray = RILUtils.arrayListToPrimitiveArray(pdu); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS); @@ -181,7 +181,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void newSmsStatusReport(int indicationType, ArrayList pdu) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); byte[] pduArray = RILUtils.arrayListToPrimitiveArray(pdu); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT); @@ -192,7 +192,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void newSmsOnSim(int indicationType, int recordNumber) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM); @@ -202,7 +202,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void onUssd(int indicationType, int ussdModeType, String msg) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType); @@ -216,7 +216,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void nitzTimeReceived(int indicationType, String nitzTime, long receivedTime) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime); @@ -241,7 +241,7 @@ public class RadioIndication extends IRadioIndication.Stub { public void currentSignalStrength(int indicationType, android.hardware.radio.V1_0.SignalStrength signalStrength) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); SignalStrength ssInitial = RILUtils.convertHalSignalStrength(signalStrength); @@ -259,7 +259,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void currentLinkCapacityEstimate(int indicationType, android.hardware.radio.V1_2.LinkCapacityEstimate lce) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); List response = RILUtils.convertHalLceData(lce); @@ -275,7 +275,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void currentLinkCapacityEstimate_1_6(int indicationType, android.hardware.radio.V1_6.LinkCapacityEstimate lce) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); List response = RILUtils.convertHalLceData(lce); @@ -291,7 +291,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void currentSignalStrength_1_2(int indicationType, android.hardware.radio.V1_2.SignalStrength signalStrength) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength); // Note this is set to "verbose" because it happens frequently @@ -308,7 +308,7 @@ public class RadioIndication extends IRadioIndication.Stub { public void currentSignalStrength_1_4(int indicationType, android.hardware.radio.V1_4.SignalStrength signalStrength) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength); @@ -325,7 +325,7 @@ public class RadioIndication extends IRadioIndication.Stub { public void currentSignalStrength_1_6(int indicationType, android.hardware.radio.V1_6.SignalStrength signalStrength) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength); @@ -341,7 +341,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void currentPhysicalChannelConfigs_1_4(int indicationType, ArrayList configs) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); physicalChannelConfigsIndication(configs); } @@ -350,7 +350,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void currentPhysicalChannelConfigs_1_6(int indicationType, ArrayList configs) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); physicalChannelConfigsIndication(configs); } @@ -359,7 +359,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void currentPhysicalChannelConfigs(int indicationType, ArrayList configs) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); physicalChannelConfigsIndication(configs); } @@ -368,7 +368,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void currentEmergencyNumberList(int indicationType, ArrayList emergencyNumberList) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); List response = new ArrayList<>(emergencyNumberList.size()); for (android.hardware.radio.V1_4.EmergencyNumber emergencyNumberHal @@ -422,7 +422,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void suppSvcNotify(int indicationType, SuppSvcNotification suppSvcNotification) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); SuppServiceNotification notification = new SuppServiceNotification(); notification.notificationType = suppSvcNotification.isMT ? 1 : 0; @@ -441,7 +441,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void stkSessionEnd(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END); @@ -451,7 +451,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void stkProactiveCommand(int indicationType, String cmd) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND); @@ -461,7 +461,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void stkEventNotify(int indicationType, String cmd) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY); @@ -471,7 +471,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void stkCallSetup(int indicationType, long timeout) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout); @@ -481,7 +481,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void simSmsStorageFull(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL); @@ -491,7 +491,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void simRefresh(int indicationType, SimRefreshResult refreshResult) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); IccRefreshResponse response = new IccRefreshResponse(); response.refreshResult = refreshResult.type; @@ -504,7 +504,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void callRing(int indicationType, boolean isGsm, CdmaSignalInfoRecord record) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); char response[] = null; @@ -527,7 +527,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void simStatusChanged(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED); @@ -535,7 +535,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void cdmaNewSms(int indicationType, CdmaSmsMessage msg) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS); @@ -546,7 +546,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void newBroadcastSms(int indicationType, ArrayList data) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); byte[] response = RILUtils.arrayListToPrimitiveArray(data); if (mRil.isLogOrTrace()) { @@ -560,7 +560,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void cdmaRuimSmsStorageFull(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL); @@ -570,7 +570,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void restrictedStateChanged(int indicationType, int state) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state); @@ -580,7 +580,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void enterEmergencyCallbackMode(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE); @@ -590,7 +590,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void cdmaCallWaiting(int indicationType, CdmaCallWaiting callWaitingRecord) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); // todo: create a CdmaCallWaitingNotification constructor that takes in these fields to make // sure no fields are missing @@ -614,7 +614,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void cdmaOtaProvisionStatus(int indicationType, int status) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int response[] = new int[1]; response[0] = status; @@ -628,7 +628,7 @@ public class RadioIndication extends IRadioIndication.Stub { public void cdmaInfoRec(int indicationType, android.hardware.radio.V1_0.CdmaInformationRecords records) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int numberOfInfoRecs = records.infoRec.size(); for (int i = 0; i < numberOfInfoRecs; i++) { @@ -724,7 +724,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void indicateRingbackTone(int indicationType, boolean start) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start); @@ -732,7 +732,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void resendIncallMute(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE); @@ -740,7 +740,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void cdmaSubscriptionSourceChanged(int indicationType, int cdmaSource) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int response[] = new int[1]; response[0] = cdmaSource; @@ -754,7 +754,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void cdmaPrlChanged(int indicationType, int version) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int response[] = new int[1]; response[0] = version; @@ -766,7 +766,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void exitEmergencyCallbackMode(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE); @@ -774,7 +774,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void rilConnected(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED); @@ -787,7 +787,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void voiceRadioTechChanged(int indicationType, int rat) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int response[] = new int[1]; response[0] = rat; @@ -803,35 +803,35 @@ public class RadioIndication extends IRadioIndication.Stub { /** Get unsolicited message for cellInfoList */ public void cellInfoList(int indicationType, ArrayList records) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); responseCellInfoList(records); } /** Get unsolicited message for cellInfoList using HAL V1_2 */ public void cellInfoList_1_2(int indicationType, ArrayList records) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); responseCellInfoList(records); } /** Get unsolicited message for cellInfoList using HAL V1_4 */ public void cellInfoList_1_4(int indicationType, ArrayList records) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); responseCellInfoList(records); } /** Get unsolicited message for cellInfoList using HAL V1_5 */ public void cellInfoList_1_5(int indicationType, ArrayList records) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); responseCellInfoList(records); } /** Get unsolicited message for cellInfoList using HAL V1_5 */ public void cellInfoList_1_6(int indicationType, ArrayList records) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); responseCellInfoList(records); } @@ -843,7 +843,7 @@ public class RadioIndication extends IRadioIndication.Stub { /** Get unsolicited message for uicc applications enablement changes. */ public void uiccApplicationsEnablementChanged(int indicationType, boolean enabled) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogRet(RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED, enabled); @@ -883,7 +883,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void imsNetworkStateChanged(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED); @@ -891,7 +891,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void subscriptionStatusChanged(int indicationType, boolean activate) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int response[] = new int[1]; response[0] = activate ? 1 : 0; @@ -905,7 +905,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void srvccStateNotify(int indicationType, int state) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int response[] = new int[1]; response[0] = state; @@ -921,7 +921,7 @@ public class RadioIndication extends IRadioIndication.Stub { public void hardwareConfigChanged( int indicationType, ArrayList configs) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); ArrayList response = RILUtils.convertHalHardwareConfigList(configs); @@ -933,7 +933,7 @@ public class RadioIndication extends IRadioIndication.Stub { public void radioCapabilityIndication(int indicationType, android.hardware.radio.V1_0.RadioCapability rc) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); RadioCapability response = RILUtils.convertHalRadioCapability(rc, mRil); @@ -944,7 +944,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void onSupplementaryServiceIndication(int indicationType, StkCcUnsolSsResult ss) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); int num; SsData ssData = new SsData(); @@ -992,7 +992,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void stkCallControlAlphaNotify(int indicationType, String alpha) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha); @@ -1002,7 +1002,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void lceData(int indicationType, LceDataInfo lce) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); List response = RILUtils.convertHalLceData(lce); @@ -1014,7 +1014,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void pcoData(int indicationType, PcoDataInfo pco) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); PcoData response = new PcoData(pco.cid, pco.bearerProto, pco.pcoId, RILUtils.arrayListToPrimitiveArray(pco.contents)); @@ -1025,7 +1025,7 @@ public class RadioIndication extends IRadioIndication.Stub { } public void modemReset(int indicationType, String reason) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason); @@ -1038,7 +1038,7 @@ public class RadioIndication extends IRadioIndication.Stub { * @param indicationType RadioIndicationType */ public void carrierInfoForImsiEncryption(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null); @@ -1055,7 +1055,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void keepaliveStatus( int indicationType, android.hardware.radio.V1_1.KeepaliveStatus halStatus) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogRet( @@ -1074,7 +1074,7 @@ public class RadioIndication extends IRadioIndication.Stub { * @param indicationType RadioIndicationType */ public void simPhonebookChanged(int indicationType) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED); @@ -1091,7 +1091,7 @@ public class RadioIndication extends IRadioIndication.Stub { */ public void simPhonebookRecordsReceived(int indicationType, byte status, ArrayList records) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); List simPhonebookRecords = new ArrayList<>(); @@ -1124,7 +1124,7 @@ public class RadioIndication extends IRadioIndication.Stub { android.hardware.radio.V1_5.CellIdentity cellIdentity, String chosenPlmn, @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); CellIdentity ci = RILUtils.convertHalCellIdentity(cellIdentity); if (ci == null || TextUtils.isEmpty(chosenPlmn) @@ -1132,7 +1132,7 @@ public class RadioIndication extends IRadioIndication.Stub { || (domain & ~NetworkRegistrationInfo.DOMAIN_CS_PS) != 0 || causeCode < 0 || additionalCauseCode < 0 || (causeCode == Integer.MAX_VALUE && additionalCauseCode == Integer.MAX_VALUE)) { - reportAnomaly( + AnomalyReporter.reportAnomaly( UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb4"), "Invalid registrationFailed indication"); @@ -1155,10 +1155,10 @@ public class RadioIndication extends IRadioIndication.Stub { public void barringInfoChanged(int indicationType, android.hardware.radio.V1_5.CellIdentity cellIdentity, ArrayList barringInfos) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (cellIdentity == null || barringInfos == null) { - reportAnomaly( + AnomalyReporter.reportAnomaly( UUID.fromString("645b16bb-c930-4c1c-9c5d-568696542e05"), "Invalid barringInfoChanged indication"); @@ -1224,22 +1224,29 @@ public class RadioIndication extends IRadioIndication.Stub { android.hardware.radio.V1_6.PhysicalChannelConfig config = (android.hardware.radio.V1_6.PhysicalChannelConfig) obj; PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder(); + int band = PhysicalChannelConfig.BAND_UNKNOWN; switch (config.band.getDiscriminator()) { case Band.hidl_discriminator.geranBand: - builder.setBand(config.band.geranBand()); + band = config.band.geranBand(); break; case Band.hidl_discriminator.utranBand: - builder.setBand(config.band.utranBand()); + band = config.band.utranBand(); break; case Band.hidl_discriminator.eutranBand: - builder.setBand(config.band.eutranBand()); + band = config.band.eutranBand(); break; case Band.hidl_discriminator.ngranBand: - builder.setBand(config.band.ngranBand()); + band = config.band.ngranBand(); break; default: mRil.riljLoge("Unsupported band " + config.band.getDiscriminator()); } + if (band == PhysicalChannelConfig.BAND_UNKNOWN) { + mRil.riljLoge("Unsupported unknown band."); + return; + } else { + builder.setBand(band); + } response.add(builder.setCellConnectionStatus( RILUtils.convertHalCellConnectionStatus(config.status)) .setDownlinkChannelNumber(config.downlinkChannelNumber) @@ -1256,8 +1263,9 @@ public class RadioIndication extends IRadioIndication.Stub { } } } catch (IllegalArgumentException iae) { - reportAnomaly(UUID.fromString("918f0970-9aa9-4bcd-a28e-e49a83fe77d5"), - "RIL reported invalid PCC (HIDL)"); + AnomalyReporter.reportAnomaly( + UUID.fromString("918f0970-9aa9-4bcd-a28e-e49a83fe77d5"), + "RIL reported invalid PCC (HIDL)"); mRil.riljLoge("Invalid PhysicalChannelConfig " + iae); return; } @@ -1270,7 +1278,7 @@ public class RadioIndication extends IRadioIndication.Stub { private void responseNetworkScan(int indicationType, android.hardware.radio.V1_1.NetworkScanResult result) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); ArrayList cellInfos = RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos)); @@ -1281,7 +1289,7 @@ public class RadioIndication extends IRadioIndication.Stub { private void responseNetworkScan_1_2(int indicationType, android.hardware.radio.V1_2.NetworkScanResult result) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); ArrayList cellInfos = RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos)); @@ -1292,7 +1300,7 @@ public class RadioIndication extends IRadioIndication.Stub { private void responseNetworkScan_1_4(int indicationType, android.hardware.radio.V1_4.NetworkScanResult result) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); ArrayList cellInfos = RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos)); @@ -1303,7 +1311,7 @@ public class RadioIndication extends IRadioIndication.Stub { private void responseNetworkScan_1_5(int indicationType, android.hardware.radio.V1_5.NetworkScanResult result) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); ArrayList cellInfos = RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos)); @@ -1314,7 +1322,7 @@ public class RadioIndication extends IRadioIndication.Stub { private void responseNetworkScan_1_6(int indicationType, android.hardware.radio.V1_6.NetworkScanResult result) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); ArrayList cellInfos = RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos)); @@ -1324,7 +1332,7 @@ public class RadioIndication extends IRadioIndication.Stub { } private void responseDataCallListChanged(int indicationType, List dcList) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList); @@ -1334,17 +1342,11 @@ public class RadioIndication extends IRadioIndication.Stub { } private void responseApnUnthrottled(int indicationType, String apn) { - mRil.processIndication(RIL.RADIO_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_RADIO, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, apn); mRil.mApnUnthrottledRegistrants.notifyRegistrants( new AsyncResult(null, apn, null)); } - - private void reportAnomaly(UUID uuid, String msg) { - Phone phone = mRil.mPhoneId == null ? null : PhoneFactory.getPhone(mRil.mPhoneId); - int carrierId = phone == null ? UNKNOWN_CARRIER_ID : phone.getCarrierId(); - AnomalyReporter.reportAnomaly(uuid, msg, carrierId); - } } diff --git a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java index 04c6b541de0aad7448fb7fbdb200c62ad13fd848..bab4d121851c5bb48e5f89cb34f601bbb26cac4d 100644 --- a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java +++ b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java @@ -28,6 +28,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.telephony.Rlog; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Collections; import java.util.Set; @@ -164,6 +166,13 @@ public class RadioInterfaceCapabilityController extends Handler { } } + /** + * Dump the fields of the instance + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("mRadioConfig=" + mRadioConfig); + } + private static void log(final String s) { Rlog.d(LOG_TAG, s); } diff --git a/src/java/com/android/internal/telephony/RadioMessagingProxy.java b/src/java/com/android/internal/telephony/RadioMessagingProxy.java index e68e957b7d7a0e21f2f500f5dee55b4a9c7974a4..69ccf3612aff59ace5b32379a26f462dffed3835 100644 --- a/src/java/com/android/internal/telephony/RadioMessagingProxy.java +++ b/src/java/com/android/internal/telephony/RadioMessagingProxy.java @@ -36,13 +36,23 @@ public class RadioMessagingProxy extends RadioServiceProxy { * Set IRadioMessaging as the AIDL implementation for RadioServiceProxy * @param halVersion Radio HAL version * @param messaging IRadioMessaging implementation + * + * @return updated HAL version */ - public void setAidl(HalVersion halVersion, + public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.messaging.IRadioMessaging messaging) { - mHalVersion = halVersion; + HalVersion version = halVersion; + try { + version = RIL.getServiceHalVersion(messaging.getInterfaceVersion()); + } catch (RemoteException e) { + Rlog.e(TAG, "setAidl: " + e); + } + mHalVersion = version; mMessagingProxy = messaging; mIsAidl = true; - Rlog.d(TAG, "AIDL initialized"); + + Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion); + return mHalVersion; } /** diff --git a/src/java/com/android/internal/telephony/RadioModemProxy.java b/src/java/com/android/internal/telephony/RadioModemProxy.java index 7aaf727b3b19a52ba04120c05aaecff73f133983..4178293d06e2d280aa3072c2d2145ab92e9ac5b7 100644 --- a/src/java/com/android/internal/telephony/RadioModemProxy.java +++ b/src/java/com/android/internal/telephony/RadioModemProxy.java @@ -31,13 +31,23 @@ public class RadioModemProxy extends RadioServiceProxy { * Set IRadioModem as the AIDL implementation for RadioServiceProxy * @param halVersion Radio HAL version * @param modem IRadioModem implementation + * + * @return updated HAL version */ - public void setAidl(HalVersion halVersion, + public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.modem.IRadioModem modem) { - mHalVersion = halVersion; + HalVersion version = halVersion; + try { + version = RIL.getServiceHalVersion(modem.getInterfaceVersion()); + } catch (RemoteException e) { + Rlog.e(TAG, "setAidl: " + e); + } + mHalVersion = version; mModemProxy = modem; mIsAidl = true; - Rlog.d(TAG, "AIDL initialized"); + + Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion); + return mHalVersion; } /** @@ -109,6 +119,19 @@ public class RadioModemProxy extends RadioServiceProxy { } } + /** + * Call IRadioModem#getImei + * + * @param serial Serial number of request + * @throws RemoteException + */ + public void getImei(int serial) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mModemProxy.getImei(serial); + } + } + /** * Call IRadioModem#getHardwareConfig * @param serial Serial number of request diff --git a/src/java/com/android/internal/telephony/RadioNetworkProxy.java b/src/java/com/android/internal/telephony/RadioNetworkProxy.java index b88103510ae741fff2d84b8d34940ed4e5c31bb4..246c2e02045be4b8a86364c4bd9c2e436c9e154a 100644 --- a/src/java/com/android/internal/telephony/RadioNetworkProxy.java +++ b/src/java/com/android/internal/telephony/RadioNetworkProxy.java @@ -65,13 +65,23 @@ public class RadioNetworkProxy extends RadioServiceProxy { * Set IRadioNetwork as the AIDL implementation for RadioServiceProxy * @param halVersion Radio HAL version * @param network IRadioNetwork implementation + * + * @return updated HAL version */ - public void setAidl(HalVersion halVersion, + public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.network.IRadioNetwork network) { - mHalVersion = halVersion; + HalVersion version = halVersion; + try { + version = RIL.getServiceHalVersion(network.getInterfaceVersion()); + } catch (RemoteException e) { + Rlog.e(TAG, "setAidl: " + e); + } + mHalVersion = version; mNetworkProxy = network; mIsAidl = true; - Rlog.d(TAG, "AIDL initialized"); + + Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion); + return mHalVersion; } /** @@ -828,4 +838,127 @@ public class RadioNetworkProxy extends RadioServiceProxy { } // Only supported on AIDL. } + + /** + * Set the Emergency Mode + * + * @param serial Serial number of the request. + * @param emcModeType Defines the radio emergency mode type. + * @throws RemoteException + */ + public void setEmergencyMode(int serial, int emcModeType) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.setEmergencyMode(serial, emcModeType); + } + // Only supported on AIDL. + } + + /** + * Triggers an Emergency network scan. + * + * @param serial Serial number of the request. + * @param scanRequest Contains the preferred networks and type of service to be scanned. + * @throws RemoteException + */ + public void triggerEmergencyNetworkScan(int serial, + android.hardware.radio.network.EmergencyNetworkScanTrigger scanRequest) + throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.triggerEmergencyNetworkScan(serial, scanRequest); + } + // Only supported on AIDL. + } + + /** + * Cancels ongoing Emergency network scan + * + * @param serial Serial number of the request. + * @param resetScan Indicates how the next {@link #triggerEmergencyNetworkScan} should work. + * If {@code true}, then the modem shall start the new scan from the beginning, + * otherwise the modem shall resume from the last search. + * + * @throws RemoteException + */ + public void cancelEmergencyNetworkScan(int serial, boolean resetScan) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.cancelEmergencyNetworkScan(serial, resetScan); + } + // Only supported on AIDL. + } + + /** + * Exits ongoing Emergency Mode + * + * @param serial Serial number of the request. + * @throws RemoteException + */ + public void exitEmergencyMode(int serial) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.exitEmergencyMode(serial); + } + // Only supported on AIDL. + } + + /** + * Set if null ciphering / null integrity is permitted. + * + * @param serial Serial number of the request. + * @param enabled true if null modes are allowed, false otherwise + * @throws RemoteException + */ + public void setNullCipherAndIntegrityEnabled(int serial, + boolean enabled) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.setNullCipherAndIntegrityEnabled(serial, enabled); + } + // Only supported on AIDL. + } + + /** + * Get if null ciphering / null integrity is permitted. + * @param serial Serial number of the request. + * @throws RemoteException + * + */ + public void isNullCipherAndIntegrityEnabled(int serial) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.isNullCipherAndIntegrityEnabled(serial); + } + // Only supported on AIDL. + } + + /** + * Checks whether N1 mode is enabled. + * + * @param serial Serial number of the request. + * @throws RemoteException + */ + public void isN1ModeEnabled(int serial) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.isN1ModeEnabled(serial); + } + // Only supported on AIDL. + } + + /** + * Enables or disables N1 mode. + * + * @param serial Serial number of request. + * @param enable Indicates whether to enable N1 mode or not. + * @throws RemoteException + */ + public void setN1ModeEnabled(int serial, boolean enable) throws RemoteException { + if (isEmpty()) return; + if (isAidl()) { + mNetworkProxy.setN1ModeEnabled(serial, enable); + } + // Only supported on AIDL. + } } diff --git a/src/java/com/android/internal/telephony/RadioServiceProxy.java b/src/java/com/android/internal/telephony/RadioServiceProxy.java index 8650dd006b302e09a3d8a99d2d903c308fb5fef9..4257327330ec9fb4ee88c8decd4f88e8e27152ea 100644 --- a/src/java/com/android/internal/telephony/RadioServiceProxy.java +++ b/src/java/com/android/internal/telephony/RadioServiceProxy.java @@ -78,4 +78,9 @@ public abstract class RadioServiceProxy { if (isEmpty()) return; if (!isAidl()) mRadioProxy.responseAcknowledgement(); } + + @Override + public String toString() { + return getClass().getSimpleName() + "[mHalVersion=" + mHalVersion + ']'; + } } diff --git a/src/java/com/android/internal/telephony/RadioSimProxy.java b/src/java/com/android/internal/telephony/RadioSimProxy.java index da5b660c31518079b3c210957bb0669fcd3f315e..7c8ee7b6ead5326b79f0f49f812ffda974bb24eb 100644 --- a/src/java/com/android/internal/telephony/RadioSimProxy.java +++ b/src/java/com/android/internal/telephony/RadioSimProxy.java @@ -41,12 +41,22 @@ public class RadioSimProxy extends RadioServiceProxy { * Set IRadioSim as the AIDL implementation for RadioServiceProxy * @param halVersion Radio HAL version * @param sim IRadioSim implementation + * + * @return updated HAL version */ - public void setAidl(HalVersion halVersion, android.hardware.radio.sim.IRadioSim sim) { - mHalVersion = halVersion; + public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.sim.IRadioSim sim) { + HalVersion version = halVersion; + try { + version = RIL.getServiceHalVersion(sim.getInterfaceVersion()); + } catch (RemoteException e) { + Rlog.e(TAG, "setAidl: " + e); + } + mHalVersion = version; mSimProxy = sim; mIsAidl = true; - Rlog.d(TAG, "AIDL initialized"); + + Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion); + return mHalVersion; } /** @@ -262,14 +272,24 @@ public class RadioSimProxy extends RadioServiceProxy { } /** - * Call IRadioSim#iccCloseLogicalChannel + * Call IRadioSim#iccCloseLogicalChannelWithSessionInfo * @param serial Serial number of request * @param channelId Channel ID of the channel to be closed + * @param isEs10 Whether the logical channel is opened for performing ES10 operations. * @throws RemoteException */ - public void iccCloseLogicalChannel(int serial, int channelId) throws RemoteException { + public void iccCloseLogicalChannel(int serial, + int channelId, boolean isEs10) throws RemoteException { if (isEmpty()) return; if (isAidl()) { + if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_1)) { + android.hardware.radio.sim.SessionInfo info = + new android.hardware.radio.sim.SessionInfo(); + info.sessionId = channelId; + info.isEs10 = isEs10; + mSimProxy.iccCloseLogicalChannelWithSessionInfo(serial, info); + return; + } mSimProxy.iccCloseLogicalChannel(serial, channelId); } else { mRadioProxy.iccCloseLogicalChannel(serial, channelId); @@ -352,7 +372,8 @@ public class RadioSimProxy extends RadioServiceProxy { if (isEmpty()) return; if (isAidl()) { mSimProxy.iccTransmitApduBasicChannel(serial, - RILUtils.convertToHalSimApduAidl(0, cla, instruction, p1, p2, p3, data)); + RILUtils.convertToHalSimApduAidl(0, cla, instruction, p1, p2, p3, data, + false, mHalVersion)); } else { mRadioProxy.iccTransmitApduBasicChannel(serial, RILUtils.convertToHalSimApdu(0, cla, instruction, p1, p2, p3, data)); @@ -373,10 +394,29 @@ public class RadioSimProxy extends RadioServiceProxy { */ public void iccTransmitApduLogicalChannel(int serial, int channel, int cla, int instruction, int p1, int p2, int p3, String data) throws RemoteException { + iccTransmitApduLogicalChannel(serial, channel, cla, instruction, p1, p2, p3, data, false); + } + + /** + * Call IRadioSim#iccTransmitApduLogicalChannel + * @param serial Serial number of request + * @param channel Channel ID of the channel to use for communication + * @param cla Class of the command + * @param instruction Instruction of the command + * @param p1 P1 value of the command + * @param p2 P2 value of the command + * @param p3 P3 value of the command + * @param data Data to be sent + * @param isEs10Command APDU is an isEs10 command or not + * @throws RemoteException + */ + public void iccTransmitApduLogicalChannel(int serial, int channel, int cla, int instruction, + int p1, int p2, int p3, String data, boolean isEs10Command) throws RemoteException { if (isEmpty()) return; if (isAidl()) { mSimProxy.iccTransmitApduLogicalChannel(serial, - RILUtils.convertToHalSimApduAidl(channel, cla, instruction, p1, p2, p3, data)); + RILUtils.convertToHalSimApduAidl(channel, cla, instruction, p1, p2, p3, data, + isEs10Command, mHalVersion)); } else { mRadioProxy.iccTransmitApduLogicalChannel(serial, RILUtils.convertToHalSimApdu(channel, cla, instruction, p1, p2, p3, data)); diff --git a/src/java/com/android/internal/telephony/RadioVoiceProxy.java b/src/java/com/android/internal/telephony/RadioVoiceProxy.java index 6ac603ba90b8100c8a6fbc0c523af6c86abb4de7..7f46424485ba2952cc8692ac5bb835b09bbd1ff8 100644 --- a/src/java/com/android/internal/telephony/RadioVoiceProxy.java +++ b/src/java/com/android/internal/telephony/RadioVoiceProxy.java @@ -35,12 +35,23 @@ public class RadioVoiceProxy extends RadioServiceProxy { * Set IRadioVoice as the AIDL implementation for RadioServiceProxy * @param halVersion Radio HAL version * @param voice IRadioVoice implementation - */ - public void setAidl(HalVersion halVersion, android.hardware.radio.voice.IRadioVoice voice) { - mHalVersion = halVersion; + * + * @return updated HAL version + */ + public HalVersion setAidl(HalVersion halVersion, + android.hardware.radio.voice.IRadioVoice voice) { + HalVersion version = halVersion; + try { + version = RIL.getServiceHalVersion(voice.getInterfaceVersion()); + } catch (RemoteException e) { + Rlog.e(TAG, "setAidl: " + e); + } + mHalVersion = version; mVoiceProxy = voice; mIsAidl = true; - Rlog.d(TAG, "AIDL initialized"); + + Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion); + return mHalVersion; } /** diff --git a/src/java/com/android/internal/telephony/RatRatcheter.java b/src/java/com/android/internal/telephony/RatRatcheter.java index bea2c2a15dbd8a94a5fa542fdec4b10711eee6e9..aff62ae41f0fa49b46e3d3dc1e77cd08490769c9 100644 --- a/src/java/com/android/internal/telephony/RatRatcheter.java +++ b/src/java/com/android/internal/telephony/RatRatcheter.java @@ -141,15 +141,24 @@ public class RatRatcheter { synchronized (mRatFamilyMap) { // Either the two technologies are the same or their families must be non-null // and the same. + // To Fix Missing Null check + if (ss1.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN) == null + || ss2.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN) == null) { + return false; + } + int dataRat1 = ServiceState.networkTypeToRilRadioTechnology( ss1.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, - AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + AccessNetworkConstants.TRANSPORT_TYPE_WWAN) .getAccessNetworkTechnology()); int dataRat2 = ServiceState.networkTypeToRilRadioTechnology( ss2.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, - AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + AccessNetworkConstants.TRANSPORT_TYPE_WWAN) .getAccessNetworkTechnology()); + // The api getAccessNetworkTechnology@NetworkRegistrationInfo always returns LTE though // data rat is LTE CA. Because it uses mIsUsingCarrierAggregation to indicate whether // it is LTE CA or not. However, we need its actual data rat to check if they are the diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java index f1afdddcf3b0991b4c479de18b3e5a3fd31cb3ac..a78242ad5a87d1804a1a215a38398cc6bfb8a346 100644 --- a/src/java/com/android/internal/telephony/SMSDispatcher.java +++ b/src/java/com/android/internal/telephony/SMSDispatcher.java @@ -27,6 +27,9 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -39,6 +42,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; import android.database.ContentObserver; import android.net.Uri; import android.os.AsyncResult; @@ -86,6 +90,7 @@ import com.android.internal.telephony.cdma.sms.UserData; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.IccRecords; +import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; import java.io.FileDescriptor; @@ -145,6 +150,8 @@ public abstract class SMSDispatcher extends Handler { /** New status report received. */ protected static final int EVENT_NEW_SMS_STATUS_REPORT = 10; + /** Retry Sending RP-SMMA Notification */ + protected static final int EVENT_RETRY_SMMA = 11; // other protected static final int EVENT_NEW_ICC_SMS = 14; protected static final int EVENT_ICC_CHANGED = 15; @@ -156,6 +163,17 @@ public abstract class SMSDispatcher extends Handler { /** Handle SIM loaded */ private static final int EVENT_SIM_LOADED = 18; + /** + * When this change is enabled, more specific values of SMS sending error code + * {@link SmsManager#Result} will be returned to the SMS Apps. + * + * Refer to {@link SMSDispatcher#rilErrorToSmsManagerResult} fore more details of the new values + * of SMS sending error code that will be returned. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + static final long ADD_MORE_SMS_SENDING_ERROR_CODES = 250017070L; + @UnsupportedAppUsage protected Phone mPhone; @UnsupportedAppUsage @@ -167,9 +185,18 @@ public abstract class SMSDispatcher extends Handler { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected final TelephonyManager mTelephonyManager; protected final LocalLog mLocalLog = new LocalLog(16); + protected final LocalLog mSmsOutgoingErrorCodes = new LocalLog(10); /** Maximum number of times to retry sending a failed SMS. */ protected static final int MAX_SEND_RETRIES = 3; + + /** Retransmitted Flag as specified in section 6.3.1.2 in TS 124011 + * true: RP-SMMA Retried once and no more transmissions are permitted + * false: not retried at all and at least another transmission of the RP-SMMA message + * is currently permitted + */ + protected boolean mRPSmmaRetried = false; + /** Delay before next send attempt on a failed SMS, in milliseconds. */ @VisibleForTesting public static final int SEND_RETRY_DELAY = 2000; @@ -299,6 +326,26 @@ public abstract class SMSDispatcher extends Handler { */ protected abstract String getFormat(); + /** + * Gets the maximum number of times the SMS can be retried upon Failure, + * from the {@link android.telephony.CarrierConfigManager} + * + * @return the default maximum number of times SMS can be sent + */ + protected int getMaxSmsRetryCount() { + return MAX_SEND_RETRIES; + } + + /** + * Gets the Time delay before next send attempt on a failed SMS, + * from the {@link android.telephony.CarrierConfigManager} + * + * @return the Time in miiliseconds for delay before next send attempt on a failed SMS + */ + protected int getSmsRetryDelayValue() { + return SEND_RETRY_DELAY; + } + /** * Called when a status report is received. This should correspond to a previously successful * SEND. @@ -400,14 +447,10 @@ public abstract class SMSDispatcher extends Handler { */ mMessageRef = getTpmrValueFromSIM(); if (mMessageRef == -1) { - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(msg.arg1); - if (subInfo != null) { - mMessageRef = subInfo.getLastUsedTPMessageReference(); - } - } else { - mMessageRef = SubscriptionController.getInstance().getMessageRef(msg.arg1); + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(msg.arg1); + if (subInfo != null) { + mMessageRef = subInfo.getLastUsedTPMessageReference(); } } break; @@ -430,12 +473,8 @@ public abstract class SMSDispatcher extends Handler { updateSIMLastTPMRValue(mMessageRef); final long identity = Binder.clearCallingIdentity(); try { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance() - .setLastUsedTPMessageReference(getSubId(), mMessageRef); - } else { - SubscriptionController.getInstance().updateMessageRef(getSubId(), mMessageRef); - } + SubscriptionManagerService.getInstance() + .setLastUsedTPMessageReference(getSubId(), mMessageRef); } catch (SecurityException e) { Rlog.e(TAG, "Security Exception caused on messageRef updation to DB " + e.getMessage()); } finally { @@ -467,17 +506,42 @@ public abstract class SMSDispatcher extends Handler { } /** - * Returns the next TP message Reference value incremented by 1 for every sms sent . - * once a max of 255 is reached TP message Reference is reset to 0. + * Returns the next TP message Reference value incremented by 1 for every sms sent . + * once a max of 255 is reached TP message Reference is reset to 0. * - * @return messageRef TP message Reference value + * @return messageRef TP message Reference value */ public int nextMessageRef() { + if (!isMessageRefIncrementViaTelephony()) { + return 0; + } + mMessageRef = (mMessageRef + 1) % 256; updateTPMessageReference(); return mMessageRef; } + /** + * As modem is using the last used TP-MR value present in SIM card, increment of + * messageRef(TP-MR) value should be prevented (config_stk_sms_send_support set to false) + * at telephony framework. In future, config_stk_sms_send_support flag will be enabled + * so that messageRef(TP-MR) increment will be done at framework side only. + * + * TODO:- Need to have new flag to control writing TP-MR value to SIM or shared prefrence. + */ + public boolean isMessageRefIncrementViaTelephony() { + boolean isMessageRefIncrementEnabled = false; + try { + isMessageRefIncrementEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_stk_sms_send_support); + } catch (NotFoundException e) { + Rlog.e(TAG, "isMessageRefIncrementViaTelephony NotFoundException Exception"); + } + + Rlog.i(TAG, "bool.config_stk_sms_send_support= " + isMessageRefIncrementEnabled); + return isMessageRefIncrementEnabled; + } + /** * Use the carrier messaging service to send a data or text SMS. */ @@ -688,26 +752,50 @@ public abstract class SMSDispatcher extends Handler { @Override public void onSendSmsComplete(int result, int messageRef) { Rlog.d(TAG, "onSendSmsComplete: result=" + result + " messageRef=" + messageRef); - if (mCallbackCalled) { - logWithLocalLog("onSendSmsComplete: unexpected call"); - AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback, - "Unexpected onSendSmsComplete", mPhone.getCarrierId()); + if (cleanupOnSendSmsComplete("onSendSmsComplete")) { return; } - mCallbackCalled = true; + final long identity = Binder.clearCallingIdentity(); try { - mSmsSender.mCarrierMessagingServiceWrapper.disconnect(); processSendSmsResponse(mSmsSender.getSmsTracker(), result, messageRef); - mSmsSender.removeTimeout(); } finally { Binder.restoreCallingIdentity(identity); } } + /** + * This method should be called only once. + */ @Override public void onSendMultipartSmsComplete(int result, int[] messageRefs) { - Rlog.e(TAG, "Unexpected onSendMultipartSmsComplete call with result: " + result); + Rlog.d(TAG, "onSendMultipartSmsComplete: result=" + result + " messageRefs=" + + Arrays.toString(messageRefs)); + if (cleanupOnSendSmsComplete("onSendMultipartSmsComplete")) { + return; + } + + final long identity = Binder.clearCallingIdentity(); + try { + processSendMultipartSmsResponse(mSmsSender.getSmsTrackers(), result, messageRefs); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private boolean cleanupOnSendSmsComplete(String callingFunction) { + if (mCallbackCalled) { + logWithLocalLog(callingFunction + ": unexpected call"); + AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback, + "Unexpected " + callingFunction, mPhone.getCarrierId()); + return true; + } + + mCallbackCalled = true; + mSmsSender.removeTimeout(); + mSmsSender.mCarrierMessagingServiceWrapper.disconnect(); + + return false; } @Override @@ -782,8 +870,7 @@ public abstract class SMSDispatcher extends Handler { } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - void sendSmsByCarrierApp(String carrierPackageName, - MultipartSmsSenderCallback senderCallback) { + void sendSmsByCarrierApp(String carrierPackageName, SmsSenderCallback senderCallback) { super.sendSmsByCarrierApp(carrierPackageName, senderCallback); } @@ -831,69 +918,6 @@ public abstract class SMSDispatcher extends Handler { } } - /** - * Callback for MultipartSmsSender from the carrier messaging service. - * Once the result is ready, the carrier messaging service connection is disposed. - */ - private final class MultipartSmsSenderCallback implements CarrierMessagingCallback { - private final MultipartSmsSender mSmsSender; - private boolean mCallbackCalled = false; - - MultipartSmsSenderCallback(MultipartSmsSender smsSender) { - mSmsSender = smsSender; - } - - @Override - public void onSendSmsComplete(int result, int messageRef) { - Rlog.e(TAG, "Unexpected onSendSmsComplete call with result: " + result); - } - - /** - * This method should be called only once. - */ - @Override - public void onSendMultipartSmsComplete(int result, int[] messageRefs) { - Rlog.d(TAG, "onSendMultipartSmsComplete: result=" + result + " messageRefs=" - + Arrays.toString(messageRefs)); - if (mCallbackCalled) { - logWithLocalLog("onSendMultipartSmsComplete: unexpected call"); - AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback, - "Unexpected onSendMultipartSmsComplete", mPhone.getCarrierId()); - return; - } - mCallbackCalled = true; - mSmsSender.removeTimeout(); - mSmsSender.mCarrierMessagingServiceWrapper.disconnect(); - - if (mSmsSender.mTrackers == null) { - Rlog.e(TAG, "Unexpected onSendMultipartSmsComplete call with null trackers."); - return; - } - - final long identity = Binder.clearCallingIdentity(); - try { - processSendMultipartSmsResponse(mSmsSender.mTrackers, result, messageRefs); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public void onReceiveSmsComplete(int result) { - Rlog.e(TAG, "Unexpected onReceiveSmsComplete call with result: " + result); - } - - @Override - public void onSendMmsComplete(int result, byte[] sendConfPdu) { - Rlog.e(TAG, "Unexpected onSendMmsComplete call with result: " + result); - } - - @Override - public void onDownloadMmsComplete(int result) { - Rlog.e(TAG, "Unexpected onDownloadMmsComplete call with result: " + result); - } - } - private void processSendMultipartSmsResponse( SmsTracker[] trackers, int result, int[] messageRefs) { if (trackers == null) { @@ -1038,7 +1062,7 @@ public abstract class SMSDispatcher extends Handler { // This is retry after failure over IMS but voice is not available. // Set retry to max allowed, so no retry is sent and cause // SmsManager.RESULT_ERROR_GENERIC_FAILURE to be returned to app. - tracker.mRetryCount = MAX_SEND_RETRIES; + tracker.mRetryCount = getMaxSmsRetryCount(); Rlog.d(TAG, "handleSendComplete: Skipping retry: " + " isIms()=" + isIms() @@ -1061,7 +1085,7 @@ public abstract class SMSDispatcher extends Handler { tracker.isFromDefaultSmsApplication(mContext), tracker.getInterval()); } else if (error == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY - && tracker.mRetryCount < MAX_SEND_RETRIES) { + && tracker.mRetryCount < getMaxSmsRetryCount()) { // Retry after a delay if needed. // TODO: According to TS 23.040, 9.2.3.6, we should resend // with the same TP-MR as the failed message, and @@ -1073,7 +1097,7 @@ public abstract class SMSDispatcher extends Handler { tracker.mRetryCount++; int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE; Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker); - sendMessageDelayed(retryMsg, SEND_RETRY_DELAY); + sendMessageDelayed(retryMsg, getSmsRetryDelayValue()); mPhone.getSmsStats().onOutgoingSms( tracker.mImsRetry > 0 /* isOverIms */, SmsConstants.FORMAT_3GPP2.equals(getFormat()), @@ -1100,8 +1124,31 @@ public abstract class SMSDispatcher extends Handler { } @SmsManager.Result - private static int rilErrorToSmsManagerResult(CommandException.Error rilError, + private int rilErrorToSmsManagerResult(CommandException.Error rilError, SmsTracker tracker) { + mSmsOutgoingErrorCodes.log("rilError: " + rilError + + ", MessageId: " + SmsController.formatCrossStackMessageId(tracker.mMessageId)); + + ApplicationInfo appInfo = tracker.getAppInfo(); + if (appInfo == null + || !CompatChanges.isChangeEnabled(ADD_MORE_SMS_SENDING_ERROR_CODES, appInfo.uid)) { + if (rilError == CommandException.Error.INVALID_RESPONSE + || rilError == CommandException.Error.SIM_PIN2 + || rilError == CommandException.Error.SIM_PUK2 + || rilError == CommandException.Error.SUBSCRIPTION_NOT_AVAILABLE + || rilError == CommandException.Error.SIM_ERR + || rilError == CommandException.Error.INVALID_SIM_STATE + || rilError == CommandException.Error.NO_SMS_TO_ACK + || rilError == CommandException.Error.SIM_BUSY + || rilError == CommandException.Error.SIM_FULL + || rilError == CommandException.Error.NO_SUBSCRIPTION + || rilError == CommandException.Error.NO_NETWORK_FOUND + || rilError == CommandException.Error.DEVICE_IN_USE + || rilError == CommandException.Error.ABORTED) { + return SmsManager.RESULT_ERROR_GENERIC_FAILURE; + } + } + switch (rilError) { case RADIO_NOT_AVAILABLE: return SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE; @@ -1151,6 +1198,34 @@ public abstract class SMSDispatcher extends Handler { return SmsManager.RESULT_RIL_ACCESS_BARRED; case BLOCKED_DUE_TO_CALL: return SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL; + case INVALID_SMSC_ADDRESS: + return SmsManager.RESULT_INVALID_SMSC_ADDRESS; + case INVALID_RESPONSE: + return SmsManager.RESULT_RIL_INVALID_RESPONSE; + case SIM_PIN2: + return SmsManager.RESULT_RIL_SIM_PIN2; + case SIM_PUK2: + return SmsManager.RESULT_RIL_SIM_PUK2; + case SUBSCRIPTION_NOT_AVAILABLE: + return SmsManager.RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE; + case SIM_ERR: + return SmsManager.RESULT_RIL_SIM_ERROR; + case INVALID_SIM_STATE: + return SmsManager.RESULT_RIL_INVALID_SIM_STATE; + case NO_SMS_TO_ACK: + return SmsManager.RESULT_RIL_NO_SMS_TO_ACK; + case SIM_BUSY: + return SmsManager.RESULT_RIL_SIM_BUSY; + case SIM_FULL: + return SmsManager.RESULT_RIL_SIM_FULL; + case NO_SUBSCRIPTION: + return SmsManager.RESULT_RIL_NO_SUBSCRIPTION; + case NO_NETWORK_FOUND: + return SmsManager.RESULT_RIL_NO_NETWORK_FOUND; + case DEVICE_IN_USE: + return SmsManager.RESULT_RIL_DEVICE_IN_USE; + case ABORTED: + return SmsManager.RESULT_RIL_ABORTED; default: Rlog.d(TAG, "rilErrorToSmsManagerResult: " + rilError + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId)); @@ -1371,11 +1446,121 @@ public abstract class SMSDispatcher extends Handler { * @param messageId An id that uniquely identifies the message requested to be sent. * Used for logging and diagnostics purposes. The id may be NULL. */ + public void sendText(String destAddr, String scAddr, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri, + String callingPkg, boolean persistMessage, int priority, + boolean expectMore, int validityPeriod, boolean isForVvm, + long messageId) { + sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg, + persistMessage, priority, expectMore, validityPeriod, isForVvm, messageId, false); + } + + /** + * Send a text based SMS. + * + * @param destAddr the address to send the message to + * @param scAddr is the service center address or null to use + * the current default SMSC + * @param text the body of the message to send + * @param sentIntent if not NULL this PendingIntent is + * broadcast when the message is successfully sent, or failed. + * The result code will be Activity.RESULT_OK for success, + * or one of these errors:
+ * SmsManager.RESULT_ERROR_GENERIC_FAILURE
+ * SmsManager.RESULT_ERROR_RADIO_OFF
+ * SmsManager.RESULT_ERROR_NULL_PDU
+ * SmsManager.RESULT_ERROR_NO_SERVICE
+ * SmsManager.RESULT_ERROR_LIMIT_EXCEEDED
+ * SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE
+ * SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED
+ * SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED
+ * SmsManager.RESULT_RADIO_NOT_AVAILABLE
+ * SmsManager.RESULT_NETWORK_REJECT
+ * SmsManager.RESULT_INVALID_ARGUMENTS
+ * SmsManager.RESULT_INVALID_STATE
+ * SmsManager.RESULT_NO_MEMORY
+ * SmsManager.RESULT_INVALID_SMS_FORMAT
+ * SmsManager.RESULT_SYSTEM_ERROR
+ * SmsManager.RESULT_MODEM_ERROR
+ * SmsManager.RESULT_NETWORK_ERROR
+ * SmsManager.RESULT_ENCODING_ERROR
+ * SmsManager.RESULT_INVALID_SMSC_ADDRESS
+ * SmsManager.RESULT_OPERATION_NOT_ALLOWED
+ * SmsManager.RESULT_INTERNAL_ERROR
+ * SmsManager.RESULT_NO_RESOURCES
+ * SmsManager.RESULT_CANCELLED
+ * SmsManager.RESULT_REQUEST_NOT_SUPPORTED
+ * SmsManager.RESULT_NO_BLUETOOTH_SERVICE
+ * SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS
+ * SmsManager.RESULT_BLUETOOTH_DISCONNECTED
+ * SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING
+ * SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY
+ * SmsManager.RESULT_SMS_SEND_RETRY_FAILED
+ * SmsManager.RESULT_REMOTE_EXCEPTION
+ * SmsManager.RESULT_NO_DEFAULT_SMS_APP
+ * SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE
+ * SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY
+ * SmsManager.RESULT_RIL_NETWORK_REJECT
+ * SmsManager.RESULT_RIL_INVALID_STATE
+ * SmsManager.RESULT_RIL_INVALID_ARGUMENTS
+ * SmsManager.RESULT_RIL_NO_MEMORY
+ * SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED
+ * SmsManager.RESULT_RIL_INVALID_SMS_FORMAT
+ * SmsManager.RESULT_RIL_SYSTEM_ERR
+ * SmsManager.RESULT_RIL_ENCODING_ERR
+ * SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS
+ * SmsManager.RESULT_RIL_MODEM_ERR
+ * SmsManager.RESULT_RIL_NETWORK_ERR
+ * SmsManager.RESULT_RIL_INTERNAL_ERR
+ * SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED
+ * SmsManager.RESULT_RIL_INVALID_MODEM_STATE
+ * SmsManager.RESULT_RIL_NETWORK_NOT_READY
+ * SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED
+ * SmsManager.RESULT_RIL_NO_RESOURCES
+ * SmsManager.RESULT_RIL_CANCELLED
+ * SmsManager.RESULT_RIL_SIM_ABSENT
+ * SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED
+ * SmsManager.RESULT_RIL_ACCESS_BARRED
+ * SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL
+ * For SmsManager.RESULT_ERROR_GENERIC_FAILURE or any of the RESULT_RIL errors, + * the sentIntent may include the extra "errorCode" containing a radio technology specific + * value, generally only useful for troubleshooting.
+ * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this PendingIntent is + * broadcast when the message is delivered to the recipient. The + * @param messageUri optional URI of the message if it is already stored in the system + * @param callingPkg the calling package name + * @param persistMessage whether to save the sent message into SMS DB for a + * non-default SMS app. + * + * @param priority Priority level of the message + * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 + * --------------------------------- + * PRIORITY | Level of Priority + * --------------------------------- + * '00' | Normal + * '01' | Interactive + * '10' | Urgent + * '11' | Emergency + * ---------------------------------- + * Any Other values included Negative considered as Invalid Priority Indicator of the message. + * @param expectMore is a boolean to indicate the sending messages through same link or not. + * @param validityPeriod Validity Period of the message in mins. + * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. + * Validity Period(Minimum) -> 5 mins + * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). + * Any Other values included Negative considered as Invalid Validity Period of the message. + * @param messageId An id that uniquely identifies the message requested to be sent. + * Used for logging and diagnostics purposes. The id may be NULL. + * @param skipShortCodeCheck Skip check for short code type destination address. + */ public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage, int priority, boolean expectMore, int validityPeriod, boolean isForVvm, - long messageId) { + long messageId, boolean skipShortCodeCheck) { Rlog.d(TAG, "sendText id: " + SmsController.formatCrossStackMessageId(messageId)); int messageRef = nextMessageRef(); SmsMessageBase.SubmitPduBase pdu = getSubmitPdu( @@ -1385,7 +1570,8 @@ public abstract class SMSDispatcher extends Handler { HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu); SmsTracker tracker = getSmsTracker(callingPkg, map, sentIntent, deliveryIntent, getFormat(), messageUri, expectMore, text, true /*isText*/, - persistMessage, priority, validityPeriod, isForVvm, messageId, messageRef); + persistMessage, priority, validityPeriod, isForVvm, messageId, messageRef, + skipShortCodeCheck); if (!sendSmsByCarrierApp(false /* isDataSms */, tracker)) { sendSubmitPdu(tracker); @@ -1640,12 +1826,10 @@ public abstract class SMSDispatcher extends Handler { String carrierPackage = getCarrierAppPackageName(); if (carrierPackage != null) { - Rlog.d(TAG, "Found carrier package " + carrierPackage - + " " + Rlog.d(TAG, "Found carrier package " + carrierPackage + " " + SmsController.formatCrossStackMessageId(getMultiTrackermessageId(trackers))); MultipartSmsSender smsSender = new MultipartSmsSender(parts, trackers); - smsSender.sendSmsByCarrierApp(carrierPackage, - new MultipartSmsSenderCallback(smsSender)); + smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender)); } else { Rlog.v(TAG, "No carrier package. " + SmsController.formatCrossStackMessageId(getMultiTrackermessageId(trackers))); @@ -1698,7 +1882,7 @@ public abstract class SMSDispatcher extends Handler { getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/, true /*persistMessage*/, priority, validityPeriod, false /* isForVvm */, - messageId, messageRef); + messageId, messageRef, false); } else { Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned " + "null " + SmsController.formatCrossStackMessageId(messageId)); @@ -1711,13 +1895,12 @@ public abstract class SMSDispatcher extends Handler { SmsHeader.toByteArray(smsHeader), encoding, smsHeader.languageTable, smsHeader.languageShiftTable, validityPeriod, messageRef); if (pdu != null) { - HashMap map = getSmsTrackerMap(destinationAddress, scAddress, - message, pdu); + HashMap map = getSmsTrackerMap(destinationAddress, scAddress, message, pdu); return getSmsTracker(callingPackage, map, sentIntent, deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/, false /*persistMessage*/, priority, validityPeriod, false /* isForVvm */, - messageId, messageRef); + messageId, messageRef, false); } else { Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned " + "null " + SmsController.formatCrossStackMessageId(messageId)); @@ -1875,7 +2058,8 @@ public abstract class SMSDispatcher extends Handler { */ boolean checkDestination(SmsTracker[] trackers) { if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION) - == PackageManager.PERMISSION_GRANTED || trackers[0].mIsForVvm) { + == PackageManager.PERMISSION_GRANTED || trackers[0].mIsForVvm + || trackers[0].mSkipShortCodeDestAddrCheck) { return true; // app is pre-approved to send to short codes } else { int rule = mPremiumSmsRule.get(); @@ -1916,6 +2100,12 @@ public abstract class SMSDispatcher extends Handler { trackers[0].mDestAddress, networkCountryIso)); } + if (smsCategory != SmsManager.SMS_CATEGORY_NOT_SHORT_CODE) { + int xmlVersion = mSmsDispatchersController.getUsageMonitor() + .getShortCodeXmlFileVersion(); + mPhone.getSmsStats().onOutgoingShortCodeSms(smsCategory, xmlVersion); + } + if (smsCategory == SmsManager.SMS_CATEGORY_NOT_SHORT_CODE || smsCategory == SmsManager.SMS_CATEGORY_FREE_SHORT_CODE || smsCategory == SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE) { @@ -2213,6 +2403,7 @@ public abstract class SMSDispatcher extends Handler { private Boolean mIsFromDefaultSmsApplication; private int mCarrierId; + private boolean mSkipShortCodeDestAddrCheck; // SMS anomaly uuid -- unexpected error from RIL private final UUID mAnomalyUnexpectedErrorFromRilUUID = UUID.fromString("43043600-ea7a-44d2-9ae6-a58567ac7886"); @@ -2223,7 +2414,7 @@ public abstract class SMSDispatcher extends Handler { SmsHeader smsHeader, boolean expectMore, String fullMessageText, int subId, boolean isText, boolean persistMessage, int userId, int priority, int validityPeriod, boolean isForVvm, long messageId, int carrierId, - int messageRef) { + int messageRef, boolean skipShortCodeDestAddrCheck) { mData = data; mSentIntent = sentIntent; mDeliveryIntent = deliveryIntent; @@ -2249,6 +2440,7 @@ public abstract class SMSDispatcher extends Handler { mIsForVvm = isForVvm; mMessageId = messageId; mCarrierId = carrierId; + mSkipShortCodeDestAddrCheck = skipShortCodeDestAddrCheck; } public HashMap getData() { @@ -2263,12 +2455,21 @@ public abstract class SMSDispatcher extends Handler { return mAppInfo != null ? mAppInfo.packageName : null; } + /** + * Get the calling Application Info + * @return Application Info + */ + public ApplicationInfo getAppInfo() { + return mAppInfo == null ? null : mAppInfo.applicationInfo; + } + /** Return if the SMS was originated from the default SMS application. */ public boolean isFromDefaultSmsApplication(Context context) { if (mIsFromDefaultSmsApplication == null) { + UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId); // Perform a lazy initialization, due to the cost of the operation. - mIsFromDefaultSmsApplication = - SmsApplication.isDefaultSmsApplication(context, getAppPackageName()); + mIsFromDefaultSmsApplication = SmsApplication.isDefaultSmsApplicationAsUser(context, + getAppPackageName(), userHandle); } return mIsFromDefaultSmsApplication; } @@ -2516,7 +2717,7 @@ public abstract class SMSDispatcher extends Handler { AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, SmsHeader smsHeader, boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage, int priority, int validityPeriod, boolean isForVvm, - long messageId, int messageRef) { + long messageId, int messageRef, boolean skipShortCodeCheck) { // Get package info via packagemanager UserHandle callingUser = UserHandle.getUserHandleForUid(Binder.getCallingUid()); final int userId = callingUser.getIdentifier(); @@ -2533,7 +2734,8 @@ public abstract class SMSDispatcher extends Handler { return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format, unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore, fullMessageText, getSubId(), isText, persistMessage, userId, priority, - validityPeriod, isForVvm, messageId, mPhone.getCarrierId(), messageRef); + validityPeriod, isForVvm, messageId, mPhone.getCarrierId(), messageRef, + skipShortCodeCheck); } protected SmsTracker getSmsTracker(String callingPackage, HashMap data, @@ -2544,17 +2746,18 @@ public abstract class SMSDispatcher extends Handler { null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/, expectMore, fullMessageText, isText, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm, - messageId, messageRef); + messageId, messageRef, false); } protected SmsTracker getSmsTracker(String callingPackage, HashMap data, PendingIntent sentIntent, PendingIntent deliveryIntent, String format, Uri messageUri, boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage, - int priority, int validityPeriod, boolean isForVvm, long messageId, int messageRef) { + int priority, int validityPeriod, boolean isForVvm, long messageId, int messageRef, + boolean skipShortCodeCheck) { return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format, null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/, expectMore, fullMessageText, isText, persistMessage, priority, validityPeriod, - isForVvm, messageId, messageRef); + isForVvm, messageId, messageRef, skipShortCodeCheck); } protected HashMap getSmsTrackerMap(String destAddr, String scAddr, @@ -2773,10 +2976,17 @@ public abstract class SMSDispatcher extends Handler { IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); pw.println(TAG); pw.increaseIndent(); + pw.println("mLocalLog:"); pw.increaseIndent(); mLocalLog.dump(fd, pw, args); pw.decreaseIndent(); + + pw.println("mSmsOutgoingErrorCodes:"); + pw.increaseIndent(); + mSmsOutgoingErrorCodes.dump(fd, pw, args); + pw.decreaseIndent(); + pw.decreaseIndent(); } } diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java old mode 100755 new mode 100644 index 69f4da7f1365621f94aacb5d4a67305253029883..50eea7f695b11b7f455115a1953e707f83c5aab0 --- a/src/java/com/android/internal/telephony/ServiceStateTracker.java +++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java @@ -71,7 +71,6 @@ import android.telephony.PhysicalChannelConfig; import android.telephony.RadioAccessFamily; import android.telephony.ServiceState; import android.telephony.ServiceState.RilRadioTechnology; -import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; @@ -92,6 +91,7 @@ import com.android.internal.telephony.cdma.EriManager; import com.android.internal.telephony.cdnr.CarrierDisplayNameData; import com.android.internal.telephony.cdnr.CarrierDisplayNameResolver; import com.android.internal.telephony.data.AccessNetworksManager; +import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback; import com.android.internal.telephony.data.DataNetwork; import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback; import com.android.internal.telephony.imsphone.ImsPhone; @@ -217,7 +217,6 @@ public class ServiceStateTracker extends Handler { private RegistrantList mNrStateChangedRegistrants = new RegistrantList(); private RegistrantList mNrFrequencyChangedRegistrants = new RegistrantList(); private RegistrantList mCssIndicatorChangedRegistrants = new RegistrantList(); - private final RegistrantList mBandwidthChangedRegistrants = new RegistrantList(); private final RegistrantList mAirplaneModeChangedRegistrants = new RegistrantList(); private final RegistrantList mAreaCodeChangedRegistrants = new RegistrantList(); @@ -321,14 +320,10 @@ public class ServiceStateTracker extends Handler { private CarrierDisplayNameResolver mCdnr; private boolean mImsRegistrationOnOff = false; - /** Radio is disabled by carrier. Radio power will not be override if this field is set */ - private boolean mRadioDisabledByCarrier = false; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private boolean mDeviceShuttingDown = false; /** Keep track of SPN display rules, so we only broadcast intent if something changes. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private boolean mSpnUpdatePending = false; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private String mCurSpn = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private String mCurDataSpn = null; @@ -347,8 +342,6 @@ public class ServiceStateTracker extends Handler { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private SubscriptionManager mSubscriptionManager; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private SubscriptionController mSubscriptionController; private SubscriptionManagerService mSubscriptionManagerService; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final SstSubscriptionsChangedListener mOnSubscriptionsChangedListener = @@ -386,7 +379,6 @@ public class ServiceStateTracker extends Handler { // If not, then the subId has changed, so we need to remember the old subId, // even if the new subId is invalid (likely). mPrevSubId = mSubId; - mSubId = curSubId; // Update voicemail count and notify message waiting changed regardless of // whether the new subId is valid. This is an exception to the general logic @@ -395,72 +387,64 @@ public class ServiceStateTracker extends Handler { // which seems desirable. mPhone.updateVoiceMail(); - if (!SubscriptionManager.isValidSubscriptionId(mSubId)) { + if (!SubscriptionManager.isValidSubscriptionId(curSubId)) { if (SubscriptionManager.isValidSubscriptionId(mPrevSubId)) { // just went from valid to invalid subId, so notify phone state listeners // with final broadcast mPhone.notifyServiceStateChangedForSubId(mOutOfServiceSS, ServiceStateTracker.this.mPrevSubId); } - // If the new subscription ID isn't valid, then we don't need to do all the - // UI updating, so we're done. - return; - } - - Context context = mPhone.getContext(); + } else { + Context context = mPhone.getContext(); - mPhone.notifyPhoneStateChanged(); + mPhone.notifyPhoneStateChanged(); - if (!SubscriptionManager.isValidSubscriptionId(mPrevSubId)) { - // just went from invalid to valid subId, so notify with current service - // state in case our service state was never broadcasted (we don't notify - // service states when the subId is invalid) - mPhone.notifyServiceStateChanged(mPhone.getServiceState()); - } + if (!SubscriptionManager.isValidSubscriptionId(mPrevSubId)) { + // just went from invalid to valid subId, so notify with current service + // state in case our service state was never broadcasted (we don't notify + // service states when the subId is invalid) + mPhone.notifyServiceStateChanged(mPhone.getServiceState()); + } - boolean restoreSelection = !context.getResources().getBoolean( - com.android.internal.R.bool.skip_restoring_network_selection); - mPhone.sendSubscriptionSettings(restoreSelection); + boolean restoreSelection = !context.getResources().getBoolean( + com.android.internal.R.bool.skip_restoring_network_selection); + mPhone.sendSubscriptionSettings(restoreSelection); - setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology()); + setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology()); - if (mSpnUpdatePending) { - mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(), mCurShowPlmn, - mCurPlmn, mCurShowSpn, mCurSpn); - mSpnUpdatePending = false; - } + // Remove old network selection sharedPreferences since SP key names are now + // changed to include subId. This will be done only once when upgrading from an + // older build that did not include subId in the names. + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( + context); + String oldNetworkSelection = sp.getString( + Phone.NETWORK_SELECTION_KEY, ""); + String oldNetworkSelectionName = sp.getString( + Phone.NETWORK_SELECTION_NAME_KEY, ""); + String oldNetworkSelectionShort = sp.getString( + Phone.NETWORK_SELECTION_SHORT_KEY, ""); + if (!TextUtils.isEmpty(oldNetworkSelection) + || !TextUtils.isEmpty(oldNetworkSelectionName) + || !TextUtils.isEmpty(oldNetworkSelectionShort)) { + SharedPreferences.Editor editor = sp.edit(); + editor.putString(Phone.NETWORK_SELECTION_KEY + curSubId, + oldNetworkSelection); + editor.putString(Phone.NETWORK_SELECTION_NAME_KEY + curSubId, + oldNetworkSelectionName); + editor.putString(Phone.NETWORK_SELECTION_SHORT_KEY + curSubId, + oldNetworkSelectionShort); + editor.remove(Phone.NETWORK_SELECTION_KEY); + editor.remove(Phone.NETWORK_SELECTION_NAME_KEY); + editor.remove(Phone.NETWORK_SELECTION_SHORT_KEY); + editor.commit(); + } - // Remove old network selection sharedPreferences since SP key names are now - // changed to include subId. This will be done only once when upgrading from an - // older build that did not include subId in the names. - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( - context); - String oldNetworkSelection = sp.getString( - Phone.NETWORK_SELECTION_KEY, ""); - String oldNetworkSelectionName = sp.getString( - Phone.NETWORK_SELECTION_NAME_KEY, ""); - String oldNetworkSelectionShort = sp.getString( - Phone.NETWORK_SELECTION_SHORT_KEY, ""); - if (!TextUtils.isEmpty(oldNetworkSelection) - || !TextUtils.isEmpty(oldNetworkSelectionName) - || !TextUtils.isEmpty(oldNetworkSelectionShort)) { - SharedPreferences.Editor editor = sp.edit(); - editor.putString(Phone.NETWORK_SELECTION_KEY + mSubId, - oldNetworkSelection); - editor.putString(Phone.NETWORK_SELECTION_NAME_KEY + mSubId, - oldNetworkSelectionName); - editor.putString(Phone.NETWORK_SELECTION_SHORT_KEY + mSubId, - oldNetworkSelectionShort); - editor.remove(Phone.NETWORK_SELECTION_KEY); - editor.remove(Phone.NETWORK_SELECTION_NAME_KEY); - editor.remove(Phone.NETWORK_SELECTION_SHORT_KEY); - editor.commit(); + // Once sub id becomes valid, we need to update the service provider name + // displayed on the UI again. The old SPN update intents sent to + // MobileSignalController earlier were actually ignored due to invalid sub id. + updateSpnDisplay(); } - - // Once sub id becomes valid, we need to update the service provider name - // displayed on the UI again. The old SPN update intents sent to - // MobileSignalController earlier were actually ignored due to invalid sub id. - updateSpnDisplay(); + mSubId = curSubId; } }; @@ -560,8 +544,12 @@ public class ServiceStateTracker extends Handler { public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action.equals(Intent.ACTION_LOCALE_CHANGED)) { + log("ACTION_LOCALE_CHANGED"); // Update emergency string or operator name, polling service state. pollState(); + // Depends on modem, ServiceState is not necessarily updated, so make sure updating + // SPN. + updateSpnDisplay(); } else if (action.equals(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) { String lastKnownNetworkCountry = intent.getStringExtra( TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY); @@ -623,6 +611,12 @@ public class ServiceStateTracker extends Handler { */ private DataNetworkControllerCallback mDataDisconnectedCallback; + /** + * AccessNetworksManagerCallback is used for preferred on the IWLAN when preferred transport + * type changed in AccessNetworksManager. + */ + private AccessNetworksManagerCallback mAccessNetworksManagerCallback = null; + public ServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) { mNitzState = TelephonyComponentFactory.getInstance() .inject(NitzStateMachine.class.getName()) @@ -652,12 +646,7 @@ public class ServiceStateTracker extends Handler { mCi.registerForCellInfoList(this, EVENT_UNSOL_CELL_INFO_LIST, null); mCi.registerForPhysicalChannelConfiguration(this, EVENT_PHYSICAL_CHANNEL_CONFIG, null); - if (mPhone.isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService = SubscriptionManagerService.getInstance(); - } else { - mSubscriptionController = SubscriptionController.getInstance(); - } - + mSubscriptionManagerService = SubscriptionManagerService.getInstance(); mSubscriptionManager = SubscriptionManager.from(phone.getContext()); mSubscriptionManager.addOnSubscriptionsChangedListener( new android.os.HandlerExecutor(this), mOnSubscriptionsChangedListener); @@ -670,7 +659,7 @@ public class ServiceStateTracker extends Handler { mAccessNetworksManager = mPhone.getAccessNetworksManager(); mOutOfServiceSS = new ServiceState(); - mOutOfServiceSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false); + mOutOfServiceSS.setOutOfService(false); for (int transportType : mAccessNetworksManager.getAvailableTransports()) { mRegStateManagers.append(transportType, new NetworkRegistrationManager( @@ -693,7 +682,7 @@ public class ServiceStateTracker extends Handler { Settings.Global.ENABLE_CELLULAR_ON_BOOT, 1); mDesiredPowerState = (enableCellularOnBoot > 0) && ! (airplaneMode > 0); if (!mDesiredPowerState) { - mRadioPowerOffReasons.add(Phone.RADIO_POWER_REASON_USER); + mRadioPowerOffReasons.add(TelephonyManager.RADIO_POWER_REASON_USER); } mRadioPowerLog.log("init : airplane mode = " + airplaneMode + " enableCellularOnBoot = " + enableCellularOnBoot); @@ -735,6 +724,23 @@ public class ServiceStateTracker extends Handler { } } }; + + mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(this::post) { + @Override + public void onPreferredTransportChanged(int networkCapability) { + // Check if preferred on IWLAN was changed in ServiceState. + boolean isIwlanPreferred = mAccessNetworksManager.isAnyApnOnIwlan(); + if (mSS.isIwlanPreferred() != isIwlanPreferred) { + log("onPreferredTransportChanged: IwlanPreferred is changed to " + + isIwlanPreferred); + mSS.setIwlanPreferred(isIwlanPreferred); + mPhone.notifyServiceStateChanged(mPhone.getServiceState()); + } + } + }; + if (mAccessNetworksManagerCallback != null) { + mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback); + } } @VisibleForTesting @@ -770,9 +776,9 @@ public class ServiceStateTracker extends Handler { } mSS = new ServiceState(); - mSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false); + mSS.setOutOfService(false); mNewSS = new ServiceState(); - mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false); + mNewSS.setOutOfService(false); mLastCellInfoReqTime = 0; mLastCellInfoList = null; mStartedGprsRegCheck = false; @@ -873,13 +879,20 @@ public class ServiceStateTracker extends Handler { mCSST.dispose(); mCSST = null; } + if (mAccessNetworksManagerCallback != null) { + mAccessNetworksManager.unregisterCallback(mAccessNetworksManagerCallback); + mAccessNetworksManagerCallback = null; + } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean getDesiredPowerState() { return mDesiredPowerState; } - public boolean getPowerStateFromCarrier() { return !mRadioDisabledByCarrier; } + + public boolean getPowerStateFromCarrier() { + return !mRadioPowerOffReasons.contains(TelephonyManager.RADIO_POWER_REASON_CARRIER); + } public List getPhysicalChannelConfigList() { return mLastPhysicalChannelConfigList; @@ -1069,7 +1082,7 @@ public class ServiceStateTracker extends Handler { * @return the current reasons for which the radio is off. */ public Set getRadioPowerOffReasons() { - return mRadioPowerOffReasons; + return Set.copyOf(mRadioPowerOffReasons); } /** @@ -1095,7 +1108,7 @@ public class ServiceStateTracker extends Handler { public void setRadioPower(boolean power, boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, boolean forceApply) { setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply, - Phone.RADIO_POWER_REASON_USER); + TelephonyManager.RADIO_POWER_REASON_USER); } /** @@ -1135,23 +1148,6 @@ public class ServiceStateTracker extends Handler { setPowerStateToDesired(forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply); } - /** - * Radio power set from carrier action. if set to false means carrier desire to turn radio off - * and radio wont be re-enabled unless carrier explicitly turn it back on. - * @param enable indicate if radio power is enabled or disabled from carrier action. - */ - public void setRadioPowerFromCarrier(boolean enable) { - boolean disableByCarrier = !enable; - if (mRadioDisabledByCarrier == disableByCarrier) { - log("setRadioPowerFromCarrier mRadioDisabledByCarrier is already " - + disableByCarrier + " Do nothing."); - return; - } - - mRadioDisabledByCarrier = disableByCarrier; - setPowerStateToDesired(); - } - /** * These two flags manage the behavior of the cell lock -- the * lock should be held if either flag is true. The intention is @@ -1651,7 +1647,8 @@ public class ServiceStateTracker extends Handler { if (ar.exception == null) { boolean enable = (boolean) ar.result; if (DBG) log("EVENT_RADIO_POWER_FROM_CARRIER: " + enable); - setRadioPowerFromCarrier(enable); + setRadioPowerForReason(enable, false, false, false, + TelephonyManager.RADIO_POWER_REASON_CARRIER); } break; @@ -1660,8 +1657,8 @@ public class ServiceStateTracker extends Handler { if (ar.exception == null) { List list = (List) ar.result; if (VDBG) { - log("EVENT_PHYSICAL_CHANNEL_CONFIG: size=" + list.size() + " list=" - + list); + log("EVENT_PHYSICAL_CHANNEL_CONFIG: list=" + list + + (list == null ? "" : ", list.size()=" + list.size())); } mLastPhysicalChannelConfigList = list; boolean hasChanged = false; @@ -1680,6 +1677,7 @@ public class ServiceStateTracker extends Handler { // Notify NR frequency, NR connection status or bandwidths changed. if (hasChanged) { mPhone.notifyServiceStateChanged(mPhone.getServiceState()); + mServiceStateChangedRegistrants.notifyRegistrants(); TelephonyMetrics.getInstance().writeServiceStateChanged( mPhone.getPhoneId(), mSS); mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS); @@ -2091,16 +2089,20 @@ public class ServiceStateTracker extends Handler { if (physicalChannelConfigs != null) { for (PhysicalChannelConfig config : physicalChannelConfigs) { if (isNrPhysicalChannelConfig(config) && isInternetPhysicalChannelConfig(config)) { - // Update the NR frequency range if there is an internet data connection + // Update the NR frequency range if there is an active internet data connection // associated with this NR physical channel channel config. - newFrequencyRange = ServiceState.getBetterNRFrequencyRange( - newFrequencyRange, config.getFrequencyRange()); - break; + // If there are multiple valid configs, use the highest frequency range value. + newFrequencyRange = Math.max(newFrequencyRange, config.getFrequencyRange()); } } } boolean hasChanged = newFrequencyRange != ss.getNrFrequencyRange(); + if (hasChanged) { + log(String.format("NR frequency range changed from %s to %s.", + ServiceState.frequencyRangeToString(ss.getNrFrequencyRange()), + ServiceState.frequencyRangeToString(newFrequencyRange))); + } ss.setNrFrequencyRange(newFrequencyRange); return hasChanged; } @@ -2131,6 +2133,11 @@ public class ServiceStateTracker extends Handler { } boolean hasChanged = newNrState != oldNrState; + if (hasChanged) { + log(String.format("NR state changed from %s to %s.", + NetworkRegistrationInfo.nrStateToString(oldNrState), + NetworkRegistrationInfo.nrStateToString(newNrState))); + } regInfo.setNrState(newNrState); ss.addNetworkRegistrationInfo(regInfo); return hasChanged; @@ -2728,7 +2735,7 @@ public class ServiceStateTracker extends Handler { private void notifySpnDisplayUpdate(CarrierDisplayNameData data) { int subId = mPhone.getSubId(); - // Update ACTION_SERVICE_PROVIDERS_UPDATED IFF any value changes + // Update ACTION_SERVICE_PROVIDERS_UPDATED if any value changes if (mSubId != subId || data.shouldShowPlmn() != mCurShowPlmn || data.shouldShowSpn() != mCurShowSpn @@ -2758,22 +2765,12 @@ public class ServiceStateTracker extends Handler { SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); - if (mPhone.isSubscriptionManagerServiceEnabled()) { - if (SubscriptionManager.isValidSubscriptionId(subId)) { - mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull( - getCarrierName(data.shouldShowPlmn(), data.getPlmn(), - data.shouldShowSpn(), data.getSpn()))); - } - } else { - if (!mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(), - data.shouldShowPlmn(), data.getPlmn(), data.shouldShowSpn(), - data.getSpn())) { - mSpnUpdatePending = true; - } + if (SubscriptionManager.isValidSubscriptionId(subId)) { + mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull( + getCarrierName(data.shouldShowPlmn(), data.getPlmn(), + data.shouldShowSpn(), data.getSpn()))); } } - - mSubId = subId; mCurShowSpn = data.shouldShowSpn(); mCurShowPlmn = data.shouldShowPlmn(); mCurSpn = data.getSpn(); @@ -3093,12 +3090,12 @@ public class ServiceStateTracker extends Handler { protected void setPowerStateToDesired(boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, boolean forceApply) { if (DBG) { - String tmpLog = "setPowerStateToDesired: mDeviceShuttingDown=" + mDeviceShuttingDown + - ", mDesiredPowerState=" + mDesiredPowerState + - ", getRadioState=" + mCi.getRadioState() + - ", mRadioDisabledByCarrier=" + mRadioDisabledByCarrier + - ", IMS reg state=" + mImsRegistrationOnOff + - ", pending radio off=" + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT); + String tmpLog = "setPowerStateToDesired: mDeviceShuttingDown=" + mDeviceShuttingDown + + ", mDesiredPowerState=" + mDesiredPowerState + + ", getRadioState=" + mCi.getRadioState() + + ", mRadioPowerOffReasons=" + mRadioPowerOffReasons + + ", IMS reg state=" + mImsRegistrationOnOff + + ", pending radio off=" + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT); log(tmpLog); mRadioPowerLog.log(tmpLog); } @@ -3110,10 +3107,10 @@ public class ServiceStateTracker extends Handler { } // If we want it on and it's off, turn it on - if (mDesiredPowerState && !mRadioDisabledByCarrier + if (mDesiredPowerState && mRadioPowerOffReasons.isEmpty() && (forceApply || mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF)) { mCi.setRadioPower(true, forEmergencyCall, isSelectedPhoneForEmergencyCall, null); - } else if ((!mDesiredPowerState || mRadioDisabledByCarrier) && mCi.getRadioState() + } else if ((!mDesiredPowerState || !mRadioPowerOffReasons.isEmpty()) && mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) { if (DBG) log("setPowerStateToDesired: powerOffRadioSafely()"); powerOffRadioSafely(); @@ -3260,7 +3257,6 @@ public class ServiceStateTracker extends Handler { + " mImsRegistrationOnOff=" + mImsRegistrationOnOff + "}"); - if (mImsRegistrationOnOff && !registered) { // moving to deregistered, only send this event if we need to re-evaluate if (getRadioPowerOffDelayTimeoutForImsRegistration() > 0) { @@ -3273,6 +3269,9 @@ public class ServiceStateTracker extends Handler { } } mImsRegistrationOnOff = registered; + + // It's possible ServiceState changes did not trigger SPN display update; we update it here. + updateSpnDisplay(); } public void onImsCapabilityChanged() { @@ -3309,7 +3308,7 @@ public class ServiceStateTracker extends Handler { nri = mNewSS.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN); - mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false); + mNewSS.setOutOfService(false); // Add the IWLAN registration info back to service state. if (nri != null) { mNewSS.addNetworkRegistrationInfo(nri); @@ -3326,7 +3325,7 @@ public class ServiceStateTracker extends Handler { nri = mNewSS.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN); - mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), true); + mNewSS.setOutOfService(true); // Add the IWLAN registration info back to service state. if (nri != null) { mNewSS.addNetworkRegistrationInfo(nri); @@ -3421,8 +3420,8 @@ public class ServiceStateTracker extends Handler { updateNrFrequencyRangeFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS); updateNrStateFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS); - if (TelephonyUtils.IS_DEBUGGABLE && mPhone.mTelephonyTester != null) { - mPhone.mTelephonyTester.overrideServiceState(mNewSS); + if (TelephonyUtils.IS_DEBUGGABLE && mPhone.getTelephonyTester() != null) { + mPhone.getTelephonyTester().overrideServiceState(mNewSS); } NetworkRegistrationInfo networkRegState = mNewSS.getNetworkRegistrationInfo( @@ -3453,14 +3452,10 @@ public class ServiceStateTracker extends Handler { mSS.getState() == ServiceState.STATE_POWER_OFF && mNewSS.getState() != ServiceState.STATE_POWER_OFF; - SparseBooleanArray hasDataAttached = new SparseBooleanArray( - mAccessNetworksManager.getAvailableTransports().length); - SparseBooleanArray hasDataDetached = new SparseBooleanArray( - mAccessNetworksManager.getAvailableTransports().length); - SparseBooleanArray hasRilDataRadioTechnologyChanged = new SparseBooleanArray( - mAccessNetworksManager.getAvailableTransports().length); - SparseBooleanArray hasDataRegStateChanged = new SparseBooleanArray( - mAccessNetworksManager.getAvailableTransports().length); + SparseBooleanArray hasDataAttached = new SparseBooleanArray(); + SparseBooleanArray hasDataDetached = new SparseBooleanArray(); + SparseBooleanArray hasRilDataRadioTechnologyChanged = new SparseBooleanArray(); + SparseBooleanArray hasDataRegStateChanged = new SparseBooleanArray(); boolean anyDataRegChanged = false; boolean anyDataRatChanged = false; boolean hasAlphaRawChanged = @@ -3557,9 +3552,6 @@ public class ServiceStateTracker extends Handler { boolean hasCssIndicatorChanged = (mSS.getCssIndicator() != mNewSS.getCssIndicator()); - boolean hasBandwidthChanged = !Arrays.equals( - mSS.getCellBandwidths(), mNewSS.getCellBandwidths()); - boolean has4gHandoff = false; boolean hasMultiApnSupport = false; boolean hasLostMultiApnSupport = false; @@ -3603,7 +3595,6 @@ public class ServiceStateTracker extends Handler { + " hasCssIndicatorChanged = " + hasCssIndicatorChanged + " hasNrFrequencyRangeChanged = " + hasNrFrequencyRangeChanged + " hasNrStateChanged = " + hasNrStateChanged - + " hasBandwidthChanged = " + hasBandwidthChanged + " hasAirplaneModeOnlChanged = " + hasAirplaneModeOnChanged); } @@ -3650,7 +3641,7 @@ public class ServiceStateTracker extends Handler { ServiceState oldMergedSS = new ServiceState(mPhone.getServiceState()); mSS = new ServiceState(mNewSS); - mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false); + mNewSS.setOutOfService(false); mCellIdentity = primaryCellIdentity; if (mSS.getState() == ServiceState.STATE_IN_SERVICE && primaryCellIdentity != null) { @@ -3696,10 +3687,6 @@ public class ServiceStateTracker extends Handler { mCssIndicatorChangedRegistrants.notifyRegistrants(); } - if (hasBandwidthChanged) { - mBandwidthChangedRegistrants.notifyRegistrants(); - } - if (hasRejectCauseChanged) { setNotification(CS_REJECT_CAUSE_ENABLED); } @@ -4500,23 +4487,11 @@ public class ServiceStateTracker extends Handler { } Context context = mPhone.getContext(); - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(mPhone.getSubId()); - if (subInfo == null || !subInfo.isVisible()) { - log("cannot setNotification on invisible subid mSubId=" + mSubId); - return; - } - } else { - SubscriptionInfo info = mSubscriptionController - .getActiveSubscriptionInfo(mPhone.getSubId(), context.getOpPackageName(), - context.getAttributionTag()); - - //if subscription is part of a group and non-primary, suppress all notifications - if (info == null || (info.isOpportunistic() && info.getGroupUuid() != null)) { - log("cannot setNotification on invisible subid mSubId=" + mSubId); - return; - } + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(mPhone.getSubId()); + if (subInfo == null || !subInfo.isVisible()) { + log("cannot setNotification on invisible subid mSubId=" + mSubId); + return; } // Needed because sprout RIL sends these when they shouldn't? @@ -5304,9 +5279,8 @@ public class ServiceStateTracker extends Handler { pw.println(" mImsRegistrationOnOff=" + mImsRegistrationOnOff); pw.println(" pending radio off event=" + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT)); - pw.println(" mRadioDisabledByCarrier" + mRadioDisabledByCarrier); + pw.println(" mRadioPowerOffReasons=" + mRadioPowerOffReasons); pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown); - pw.println(" mSpnUpdatePending=" + mSpnUpdatePending); pw.println(" mCellInfoMinIntervalMs=" + mCellInfoMinIntervalMs); pw.println(" mEriManager=" + mEriManager); @@ -5529,8 +5503,7 @@ public class ServiceStateTracker extends Handler { } /** - * This method adds IWLAN registration info for legacy mode devices camped on IWLAN. It also - * makes some adjustments when the device camps on IWLAN in airplane mode. + * This method makes some adjustments when the device camps on IWLAN in airplane mode. */ private void processIwlanRegistrationInfo() { if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) { @@ -5544,7 +5517,7 @@ public class ServiceStateTracker extends Handler { } // operator info should be kept in SS String operator = mNewSS.getOperatorAlphaLong(); - mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), true); + mNewSS.setOutOfService(true); if (resetIwlanRatVal) { mNewSS.setDataRegState(ServiceState.STATE_IN_SERVICE); NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() @@ -5554,17 +5527,6 @@ public class ServiceStateTracker extends Handler { .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) .build(); mNewSS.addNetworkRegistrationInfo(nri); - if (mAccessNetworksManager.isInLegacyMode()) { - // If in legacy mode, simulate the behavior that IWLAN registration info - // is reported through WWAN transport. - nri = new NetworkRegistrationInfo.Builder() - .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) - .setDomain(NetworkRegistrationInfo.DOMAIN_PS) - .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN) - .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) - .build(); - mNewSS.addNetworkRegistrationInfo(nri); - } mNewSS.setOperatorAlphaLong(operator); // Since it's in airplane mode, cellular must be out of service. The only possible // transport for data to go through is the IWLAN transport. Setting this to true @@ -5574,31 +5536,6 @@ public class ServiceStateTracker extends Handler { } return; } - - // If the device operates in legacy mode and camps on IWLAN, modem reports IWLAN as a RAT - // through WWAN registration info. To be consistent with the behavior with AP-assisted mode, - // we manually make a WLAN registration info for clients to consume. In this scenario, - // both WWAN and WLAN registration info are the IWLAN registration info and that's the - // unfortunate limitation we have when the device operates in legacy mode. In AP-assisted - // mode, the WWAN registration will correctly report the actual cellular registration info - // when the device camps on IWLAN. - if (mAccessNetworksManager.isInLegacyMode()) { - NetworkRegistrationInfo wwanNri = mNewSS.getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - if (wwanNri != null && wwanNri.getAccessNetworkTechnology() - == TelephonyManager.NETWORK_TYPE_IWLAN) { - NetworkRegistrationInfo wlanNri = new NetworkRegistrationInfo.Builder() - .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) - .setDomain(NetworkRegistrationInfo.DOMAIN_PS) - .setRegistrationState(wwanNri.getNetworkRegistrationState()) - .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN) - .setRejectCause(wwanNri.getRejectCause()) - .setEmergencyOnly(wwanNri.isEmergencyEnabled()) - .setAvailableServices(wwanNri.getAvailableServices()) - .build(); - mNewSS.addNetworkRegistrationInfo(wlanNri); - } - } } /** @@ -5855,25 +5792,6 @@ public class ServiceStateTracker extends Handler { mCssIndicatorChangedRegistrants.remove(h); } - /** - * Registers for cell bandwidth changed. - * @param h handler to notify - * @param what what code of message when delivered - * @param obj placed in Message.obj - */ - public void registerForBandwidthChanged(Handler h, int what, Object obj) { - Registrant r = new Registrant(h, what, obj); - mBandwidthChangedRegistrants.add(r); - } - - /** - * Unregisters for cell bandwidth changed. - * @param h handler to notify - */ - public void unregisterForBandwidthChanged(Handler h) { - mBandwidthChangedRegistrants.remove(h); - } - /** * Get the NR data connection context ids. * diff --git a/src/java/com/android/internal/telephony/SignalStrengthController.java b/src/java/com/android/internal/telephony/SignalStrengthController.java index 150df573f11cbb13f67e69c497a71882c793a9f9..8c35e571f5f8a3aa73e69d61c803de866a92ef5a 100644 --- a/src/java/com/android/internal/telephony/SignalStrengthController.java +++ b/src/java/com/android/internal/telephony/SignalStrengthController.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -347,16 +349,9 @@ public class SignalStrengthController extends Handler { || (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS); if (!isStale) return false; - List subInfoList; - if (mPhone.isSubscriptionManagerServiceEnabled()) { - subInfoList = SubscriptionManagerService.getInstance().getActiveSubscriptionInfoList( - mPhone.getContext().getOpPackageName(), - mPhone.getContext().getAttributionTag()); - } else { - subInfoList = SubscriptionController.getInstance() - .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(), - mPhone.getContext().getAttributionTag()); - } + List subInfoList = SubscriptionManagerService.getInstance() + .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(), + mPhone.getContext().getAttributionTag()); if (!ArrayUtils.isEmpty(subInfoList)) { for (SubscriptionInfo info : subInfoList) { @@ -420,7 +415,7 @@ public class SignalStrengthController extends Handler { (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRP) != 0)); } - if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { + if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { int[] lteRsrqThresholds = mCarrierConfig.getIntArray( CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY); if (lteRsrqThresholds != null) { @@ -477,6 +472,18 @@ public class SignalStrengthController extends Handler { AccessNetworkConstants.AccessNetworkType.NGRAN, (nrMeasurementEnabled & CellSignalStrengthNr.USE_SSSINR) != 0)); } + + int[] wcdmaEcnoThresholds = mCarrierConfig.getIntArray( + CarrierConfigManager.KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY); + if (wcdmaEcnoThresholds != null) { + signalThresholdInfos.add( + createSignalThresholdsInfo( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO, + wcdmaEcnoThresholds, + AccessNetworkConstants.AccessNetworkType.UTRAN, + false)); + } + } consolidatedAndSetReportingCriteria(signalThresholdInfos); @@ -510,7 +517,7 @@ public class SignalStrengthController extends Handler { AccessNetworkConstants.AccessNetworkType.CDMA2000, true)); - if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { + if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { signalThresholdInfos.add( createSignalThresholdsInfo( SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ, @@ -543,6 +550,12 @@ public class SignalStrengthController extends Handler { AccessNetworkThresholds.NGRAN_SSSINR, AccessNetworkConstants.AccessNetworkType.NGRAN, false)); + signalThresholdInfos.add( + createSignalThresholdsInfo( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO, + AccessNetworkThresholds.UTRAN_ECNO, + AccessNetworkConstants.AccessNetworkType.UTRAN, + false)); } consolidatedAndSetReportingCriteria(signalThresholdInfos); @@ -571,12 +584,14 @@ public class SignalStrengthController extends Handler { measurementType, mPhone.getSubId(), mPhone.isDeviceIdle()); + int hysteresisDb = getMinimumHysteresisDb(isEnabledForAppRequest, ran, measurementType, + consolidatedThresholds); consolidatedSignalThresholdInfos.add( new SignalThresholdInfo.Builder() .setRadioAccessNetworkType(ran) .setSignalMeasurementType(measurementType) .setHysteresisMs(REPORTING_HYSTERESIS_MILLIS) - .setHysteresisDb(REPORTING_HYSTERESIS_DB) + .setHysteresisDb(hysteresisDb) .setThresholds(consolidatedThresholds, true /*isSystem*/) .setIsEnabled(isEnabledForSystem || isEnabledForAppRequest) .build()); @@ -587,6 +602,126 @@ public class SignalStrengthController extends Handler { + consolidatedSignalThresholdInfos); } + /** + * Return the minimum hysteresis dB from all available sources: + * - system default + * - value set by client through API + * - threshold delta + */ + @VisibleForTesting + public int getMinimumHysteresisDb(boolean isEnabledForAppRequest, int ran, int measurementType, + final int[] consolidatedThresholdList) { + + int currHysteresisDb = getHysteresisDbFromCarrierConfig(ran, measurementType); + + if (isEnabledForAppRequest) { + // Get minimum hysteresisDb at api + int apiHysteresisDb = + getHysteresisDbFromSignalThresholdInfoRequests(ran, measurementType); + + // Choose minimum of hysteresisDb between api Vs current system/cc value set + currHysteresisDb = Math.min(currHysteresisDb, apiHysteresisDb); + + // Hal Req: choose hysteresis db value to be smaller of smallest of threshold delta + currHysteresisDb = computeHysteresisDbOnSmallestThresholdDelta( + currHysteresisDb, consolidatedThresholdList); + } + return currHysteresisDb; + } + + /** + * Get the hysteresis db value from Signal Requests + * Note: Based on the current use case, there does not exist multile App signal threshold info + * requests with hysteresis db value, so this logic picks the latest hysteresis db value set. + * + * TODO(b/262655157): Support Multiple App Hysteresis DB value customisation + */ + private int getHysteresisDbFromSignalThresholdInfoRequests( + @AccessNetworkConstants.RadioAccessNetworkType int ran, + @SignalThresholdInfo.SignalMeasurementType int measurement) { + int apiHysteresisDb = REPORTING_HYSTERESIS_DB; + for (SignalRequestRecord record : mSignalRequestRecords) { + for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) { + if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)) { + if (info.getHysteresisDb() >= 0) { + apiHysteresisDb = info.getHysteresisDb(); + } + } + } + } + return apiHysteresisDb; + } + + private int getHysteresisDbFromCarrierConfig(int ran, int measurement) { + int configHysteresisDb = REPORTING_HYSTERESIS_DB; + String configKey = null; + + switch (ran) { + case AccessNetworkConstants.AccessNetworkType.GERAN: + if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) { + configKey = CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT; + } + break; + case AccessNetworkConstants.AccessNetworkType.UTRAN: + if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP) { + configKey = CarrierConfigManager.KEY_UTRAN_RSCP_HYSTERESIS_DB_INT; + } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO) { + configKey = CarrierConfigManager.KEY_UTRAN_ECNO_HYSTERESIS_DB_INT; + } + break; + case AccessNetworkConstants.AccessNetworkType.EUTRAN: + if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP) { + configKey = CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT; + } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ) { + configKey = CarrierConfigManager.KEY_EUTRAN_RSRQ_HYSTERESIS_DB_INT; + } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR) { + configKey = CarrierConfigManager.KEY_EUTRAN_RSSNR_HYSTERESIS_DB_INT; + } + break; + case AccessNetworkConstants.AccessNetworkType.NGRAN: + if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP) { + configKey = CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT; + } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ) { + configKey = CarrierConfigManager.KEY_NGRAN_SSRSRQ_HYSTERESIS_DB_INT; + } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) { + configKey = CarrierConfigManager.KEY_NGRAN_SSSINR_HYSTERESIS_DB_INT; + } + break; + default: + localLog("No matching configuration"); + } + if (configKey != null) { + configHysteresisDb = mCarrierConfig.getInt(configKey, REPORTING_HYSTERESIS_DB); + } + return configHysteresisDb >= SignalThresholdInfo.HYSTERESIS_DB_MINIMUM + ? configHysteresisDb : REPORTING_HYSTERESIS_DB; + } + + /** + * This method computes the hysteresis db value between smaller of the smallest Threshold Delta + * and system / cc / api hysteresis db value determined. + * + * @param currMinHysteresisDb smaller value between system / cc / api hysteresis db value + * @param signalThresholdInfoArray consolidated threshold info with App request consolidated. + * @return current minimum hysteresis db value computed between above params. + * + */ + private int computeHysteresisDbOnSmallestThresholdDelta( + int currMinHysteresisDb, final int[] signalThresholdInfoArray) { + int index = 0; + if (signalThresholdInfoArray.length > 1) { + while (index != signalThresholdInfoArray.length - 1) { + if (signalThresholdInfoArray[index + 1] - signalThresholdInfoArray[index] + < currMinHysteresisDb) { + currMinHysteresisDb = + signalThresholdInfoArray[index + 1] - signalThresholdInfoArray[index]; + } + index++; + } + } + return currMinHysteresisDb; + } + void setSignalStrengthDefaultValues() { mSignalStrength = new SignalStrength(); mSignalStrengthUpdatedTime = System.currentTimeMillis(); @@ -1125,6 +1260,16 @@ public class SignalStrengthController extends Handler { 15, /* SIGNAL_STRENGTH_GOOD */ 30 /* SIGNAL_STRENGTH_GREAT */ }; + + /** + * List of dBm thresholds for UTRAN {@link AccessNetworkConstants.AccessNetworkType} ECNO + */ + public static final int[] UTRAN_ECNO = new int[]{ + -24, /* SIGNAL_STRENGTH_POOR */ + -14, /* SIGNAL_STRENGTH_MODERATE */ + -6, /* SIGNAL_STRENGTH_GOOD */ + 1 /* SIGNAL_STRENGTH_GREAT */ + }; } private static void log(String msg) { diff --git a/src/java/com/android/internal/telephony/SimIndication.java b/src/java/com/android/internal/telephony/SimIndication.java index 20f89da73f9ba43bbd2bf86196f0fb84c5f0a993..d74a9b11949bdd1d73b98b0d300e73be2dcc7f60 100644 --- a/src/java/com/android/internal/telephony/SimIndication.java +++ b/src/java/com/android/internal/telephony/SimIndication.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_SIM; + import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED; @@ -53,7 +55,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * @param indicationType Type of radio indication */ public void carrierInfoForImsiEncryption(int indicationType) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null); @@ -69,7 +71,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * @param cdmaSource New CdmaSubscriptionSource */ public void cdmaSubscriptionSourceChanged(int indicationType, int cdmaSource) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); int[] response = new int[]{cdmaSource}; if (mRil.isLogOrTrace()) { @@ -85,7 +87,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * @param indicationType Type of radio indication */ public void simPhonebookChanged(int indicationType) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED); @@ -102,7 +104,7 @@ public class SimIndication extends IRadioSimIndication.Stub { */ public void simPhonebookRecordsReceived(int indicationType, byte status, android.hardware.radio.sim.PhonebookRecordInfo[] records) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); List simPhonebookRecords = new ArrayList<>(); @@ -127,7 +129,7 @@ public class SimIndication extends IRadioSimIndication.Stub { */ public void simRefresh(int indicationType, android.hardware.radio.sim.SimRefreshResult refreshResult) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); IccRefreshResponse response = new IccRefreshResponse(); response.refreshResult = refreshResult.type; @@ -144,7 +146,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * @param indicationType Type of radio indication */ public void simStatusChanged(int indicationType) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED); @@ -159,7 +161,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * Refer to TS 102.223 section 9.4 for command types */ public void stkEventNotify(int indicationType, String cmd) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY); @@ -175,7 +177,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * Refer to TS 102.223 section 9.4 for command types */ public void stkProactiveCommand(int indicationType, String cmd) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND); @@ -189,7 +191,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * @param indicationType Type of radio indication */ public void stkSessionEnd(int indicationType) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END); @@ -204,7 +206,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * @param activate false for subscription deactivated, true for subscription activated */ public void subscriptionStatusChanged(int indicationType, boolean activate) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); int[] response = new int[]{activate ? 1 : 0}; @@ -222,7 +224,7 @@ public class SimIndication extends IRadioSimIndication.Stub { * @param enabled Whether uiccApplications are enabled or disabled */ public void uiccApplicationsEnablementChanged(int indicationType, boolean enabled) { - mRil.processIndication(RIL.SIM_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_SIM, indicationType); if (mRil.isLogOrTrace()) { mRil.unsljLogRet(RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED, enabled); diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java index b0099fbf281309f77a63ff6cad6b80a7f92d5cc1..1e1dbe5371112937cbedcd969a108430a2ba4ee7 100644 --- a/src/java/com/android/internal/telephony/SimResponse.java +++ b/src/java/com/android/internal/telephony/SimResponse.java @@ -16,8 +16,11 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_SIM; + import android.hardware.radio.RadioError; import android.hardware.radio.RadioResponseInfo; +import android.hardware.radio.sim.CarrierRestrictions; import android.hardware.radio.sim.IRadioSimResponse; import android.telephony.CarrierRestrictionRules; import android.telephony.TelephonyManager; @@ -41,7 +44,7 @@ public class SimResponse extends IRadioSimResponse.Stub { private void responseIccIo(RadioResponseInfo responseInfo, android.hardware.radio.sim.IccIoResult result) { - RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo); if (rr != null) { IccIoResult ret = new IccIoResult(result.sw1, result.sw2, result.simResponse); @@ -68,7 +71,7 @@ public class SimResponse extends IRadioSimResponse.Stub { */ public void areUiccApplicationsEnabledResponse(RadioResponseInfo responseInfo, boolean enabled) { - RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { @@ -83,7 +86,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown. */ public void changeIccPin2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts); } /** @@ -91,14 +94,14 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown. */ public void changeIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts); } /** * @param responseInfo Response info struct containing response type, serial no. and error. */ public void enableUiccApplicationsResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** @@ -109,7 +112,7 @@ public class SimResponse extends IRadioSimResponse.Stub { public void getAllowedCarriersResponse(RadioResponseInfo responseInfo, android.hardware.radio.sim.CarrierRestrictions carrierRestrictions, int multiSimPolicy) { - RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo); if (rr == null) { return; } @@ -125,7 +128,6 @@ public class SimResponse extends IRadioSimResponse.Stub { if (!carrierRestrictions.allowedCarriersPrioritized) { carrierRestrictionDefault = CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED; } - ret = CarrierRestrictionRules.newBuilder() .setAllowedCarriers(RILUtils.convertHalCarrierList( carrierRestrictions.allowedCarriers)) @@ -133,6 +135,7 @@ public class SimResponse extends IRadioSimResponse.Stub { carrierRestrictions.excludedCarriers)) .setDefaultCarrierRestriction(carrierRestrictionDefault) .setMultiSimPolicy(policy) + .setCarrierRestrictionStatus(carrierRestrictions.status) .build(); if (responseInfo.error == RadioError.NONE) { @@ -154,7 +157,7 @@ public class SimResponse extends IRadioSimResponse.Stub { public void getCdmaSubscriptionResponse(RadioResponseInfo responseInfo, String mdn, String hSid, String hNid, String min, String prl) { RadioResponse.responseStrings( - RIL.SIM_SERVICE, mRil, responseInfo, mdn, hSid, hNid, min, prl); + HAL_SERVICE_SIM, mRil, responseInfo, mdn, hSid, hNid, min, prl); } /** @@ -162,7 +165,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param source CDMA subscription source */ public void getCdmaSubscriptionSourceResponse(RadioResponseInfo responseInfo, int source) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, source); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, source); } /** @@ -171,7 +174,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * specified barring facility is active. "0" means "disabled for all" */ public void getFacilityLockForAppResponse(RadioResponseInfo responseInfo, int response) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, response); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, response); } /** @@ -180,7 +183,7 @@ public class SimResponse extends IRadioSimResponse.Stub { */ public void getIccCardStatusResponse(RadioResponseInfo responseInfo, android.hardware.radio.sim.CardStatus cardStatus) { - RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo); if (rr != null) { IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus); @@ -197,7 +200,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param imsi String containing the IMSI */ public void getImsiForAppResponse(RadioResponseInfo responseInfo, String imsi) { - RadioResponse.responseString(RIL.SIM_SERVICE, mRil, responseInfo, imsi); + RadioResponse.responseString(HAL_SERVICE_SIM, mRil, responseInfo, imsi); } /** @@ -207,7 +210,7 @@ public class SimResponse extends IRadioSimResponse.Stub { public void getSimPhonebookCapacityResponse(RadioResponseInfo responseInfo, android.hardware.radio.sim.PhonebookCapacity pbCapacity) { AdnCapacity capacity = RILUtils.convertHalPhonebookCapacity(pbCapacity); - RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { RadioResponse.sendMessageResponse(rr.mResult, capacity); @@ -220,14 +223,21 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error. */ public void getSimPhonebookRecordsResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void iccCloseLogicalChannelResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); + } + + /** + * @param responseInfo Response info struct containing response type, serial no. and error + */ + public void iccCloseLogicalChannelWithSessionInfoResponse(RadioResponseInfo responseInfo) { + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** @@ -252,7 +262,7 @@ public class SimResponse extends IRadioSimResponse.Stub { for (int i = 0; i < selectResponse.length; i++) { arr.add((int) selectResponse[i]); } - RadioResponse.responseIntArrayList(RIL.SIM_SERVICE, mRil, responseInfo, arr); + RadioResponse.responseIntArrayList(HAL_SERVICE_SIM, mRil, responseInfo, arr); } /** @@ -277,7 +287,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void reportStkServiceIsRunningResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** @@ -286,7 +296,7 @@ public class SimResponse extends IRadioSimResponse.Stub { */ public void requestIccSimAuthenticationResponse(RadioResponseInfo responseInfo, android.hardware.radio.sim.IccIoResult iccIo) { - RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo); if (rr != null) { IccIoResult ret = new IccIoResult(iccIo.sw1, iccIo.sw2, @@ -315,7 +325,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * string starting with first byte of response */ public void sendEnvelopeResponse(RadioResponseInfo responseInfo, String commandResponse) { - RadioResponse.responseString(RIL.SIM_SERVICE, mRil, responseInfo, commandResponse); + RadioResponse.responseString(HAL_SERVICE_SIM, mRil, responseInfo, commandResponse); } /** @@ -331,7 +341,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void sendTerminalResponseToSimResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** @@ -339,7 +349,7 @@ public class SimResponse extends IRadioSimResponse.Stub { */ public void setAllowedCarriersResponse(RadioResponseInfo responseInfo) { int ret = TelephonyManager.SET_CARRIER_RESTRICTION_ERROR; - RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo); if (rr != null) { mRil.riljLog("setAllowedCarriersResponse - error = " + responseInfo.error); @@ -355,14 +365,14 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCarrierInfoForImsiEncryptionResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCdmaSubscriptionSourceResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** @@ -370,21 +380,21 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param retry 0 is the number of retries remaining, or -1 if unknown */ public void setFacilityLockForAppResponse(RadioResponseInfo responseInfo, int retry) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, retry); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, retry); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setSimCardPowerResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setUiccSubscriptionResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo); } /** @@ -392,7 +402,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown. */ public void supplyIccPin2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts); } /** @@ -400,7 +410,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown. */ public void supplyIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts); } /** @@ -408,7 +418,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown. */ public void supplyIccPuk2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts); } /** @@ -416,7 +426,7 @@ public class SimResponse extends IRadioSimResponse.Stub { * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown. */ public void supplyIccPukForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts); } /** @@ -428,7 +438,7 @@ public class SimResponse extends IRadioSimResponse.Stub { public void supplySimDepersonalizationResponse(RadioResponseInfo responseInfo, int persoType, int remainingRetries) { RadioResponse.responseInts( - RIL.SIM_SERVICE, mRil, responseInfo, persoType, remainingRetries); + HAL_SERVICE_SIM, mRil, responseInfo, persoType, remainingRetries); } /** @@ -437,7 +447,7 @@ public class SimResponse extends IRadioSimResponse.Stub { */ public void updateSimPhonebookRecordsResponse(RadioResponseInfo responseInfo, int updatedRecordIndex) { - RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, updatedRecordIndex); + RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, updatedRecordIndex); } @Override diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java index 2f3995853c6cf5279c049413d5aff2522ef0daa0..ecd62765a133de9c532c19461ed2d4dfe33128a7 100644 --- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java +++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java @@ -260,15 +260,8 @@ public class SmsBroadcastUndelivered { * Send tracker to appropriate (3GPP or 3GPP2) inbound SMS handler for broadcast. */ private static void broadcastSms(InboundSmsTracker tracker) { - InboundSmsHandler handler; int subId = tracker.getSubId(); - int phoneId; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId); - } else { - // TODO consider other subs in this subId's group as well - phoneId = SubscriptionController.getInstance().getPhoneId(subId); - } + int phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId); if (!SubscriptionManager.isValidPhoneId(phoneId)) { Rlog.e(TAG, "broadcastSms: ignoring message; no phone found for subId " + subId); return; @@ -279,7 +272,7 @@ public class SmsBroadcastUndelivered { + " phoneId " + phoneId); return; } - handler = phone.getInboundSmsHandler(tracker.is3gpp2()); + InboundSmsHandler handler = phone.getInboundSmsHandler(tracker.is3gpp2()); if (handler != null) { handler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS, tracker); } else { diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java index 08f7dd05be279ac0f60be30b38b9c09ff7459fe6..97161f85b8a9f7f6e5b0ff4300f4531eecfec9ae 100644 --- a/src/java/com/android/internal/telephony/SmsController.java +++ b/src/java/com/android/internal/telephony/SmsController.java @@ -18,6 +18,8 @@ package com.android.internal.telephony; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static com.android.internal.telephony.util.TelephonyUtils.checkDumpPermission; import android.annotation.Nullable; @@ -26,13 +28,13 @@ import android.app.AppOpsManager; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.BaseBundle; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.TelephonyServiceManager.ServiceRegisterer; -import android.os.UserHandle; import android.provider.Telephony.Sms.Intents; import android.telephony.CarrierConfigManager; import android.telephony.SmsManager; @@ -43,6 +45,7 @@ import android.telephony.TelephonyManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.subscription.SubscriptionManagerService; +import com.android.internal.telephony.util.TelephonyUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.telephony.Rlog; @@ -156,11 +159,13 @@ public class SmsController extends ISmsImplBase { if (callingPackage == null) { callingPackage = getCallingPackage(); } + Rlog.d(LOG_TAG, "sendDataForSubscriber caller=" + callingPackage); // Check if user is associated with the subscription if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, - Binder.getCallingUserHandle())) { - // TODO(b/258629881): Display error dialog. + Binder.getCallingUserHandle(), destAddr)) { + TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, + Binder.getCallingUid(), callingPackage); sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED); return; } @@ -206,9 +211,48 @@ public class SmsController extends ISmsImplBase { String callingAttributionTag, String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp, long messageId) { + sendTextForSubscriber(subId, callingPackage, callingAttributionTag, destAddr, scAddr, + text, sentIntent, deliveryIntent, persistMessageForNonDefaultSmsApp, messageId, + false, false); + + } + + /** + * @param subId Subscription Id + * @param callingAttributionTag the attribution tag of the caller + * @param destAddr the address to send the message to + * @param scAddr is the service center address or null to use + * the current default SMSC + * @param text the body of the message to send + * @param sentIntent if not NULL this PendingIntent is + * broadcast when the message is successfully sent, or failed. + * The result code will be Activity.RESULT_OK for success, or relevant errors + * the sentIntent may include the extra "errorCode" containing a radio technology specific + * value, generally only useful for troubleshooting. + * @param deliveryIntent if not NULL this PendingIntent is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * @param skipFdnCheck if set to true, FDN check must be skipped .This is set in case of STK sms + * + * @hide + */ + public void sendTextForSubscriber(int subId, String callingPackage, + String callingAttributionTag, String destAddr, String scAddr, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent, + boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipFdnCheck, + boolean skipShortCodeCheck) { if (callingPackage == null) { callingPackage = getCallingPackage(); } + Rlog.d(LOG_TAG, "sendTextForSubscriber caller=" + callingPackage); + + if (skipFdnCheck || skipShortCodeCheck) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MODIFY_PHONE_STATE permission."); + } + } if (!getSmsPermissions(subId).checkCallingCanSendText(persistMessageForNonDefaultSmsApp, callingPackage, callingAttributionTag, "Sending SMS message")) { sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE); @@ -216,15 +260,21 @@ public class SmsController extends ISmsImplBase { } // Check if user is associated with the subscription - if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, - Binder.getCallingUserHandle())) { - // TODO(b/258629881): Display error dialog. + boolean crossUserFullGranted = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED; + Rlog.d(LOG_TAG, "sendTextForSubscriber: caller has INTERACT_ACROSS_USERS_FULL? " + + crossUserFullGranted); + if (!crossUserFullGranted + && !TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, + Binder.getCallingUserHandle(), destAddr)) { + TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, + Binder.getCallingUid(), callingPackage); sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED); return; } // Perform FDN check - if (isNumberBlockedByFDN(subId, destAddr, callingPackage)) { + if (!skipFdnCheck && isNumberBlockedByFDN(subId, destAddr, callingPackage)) { sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE); return; } @@ -241,7 +291,7 @@ public class SmsController extends ISmsImplBase { sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent); } else { sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent, - persistMessageForNonDefaultSmsApp, messageId); + persistMessageForNonDefaultSmsApp, messageId, skipShortCodeCheck); } } @@ -258,16 +308,17 @@ public class SmsController extends ISmsImplBase { private void sendIccText(int subId, String callingPackage, String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, - boolean persistMessageForNonDefaultSmsApp, long messageId) { + boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck) { Rlog.d(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr" - + " Subscription: " + subId + " id: " + messageId); + + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId)); IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId); if (iccSmsIntMgr != null) { iccSmsIntMgr.sendText(callingPackage, destAddr, scAddr, text, sentIntent, - deliveryIntent, persistMessageForNonDefaultSmsApp, messageId); + deliveryIntent, persistMessageForNonDefaultSmsApp, messageId, + skipShortCodeCheck); } else { Rlog.e(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr is null for" - + " Subscription: " + subId + " id: " + messageId); + + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId)); sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE); } } @@ -295,11 +346,13 @@ public class SmsController extends ISmsImplBase { if (callingPackage == null) { callingPackage = getCallingPackage(); } + Rlog.d(LOG_TAG, "sendTextForSubscriberWithOptions caller=" + callingPackage); // Check if user is associated with the subscription if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, - Binder.getCallingUserHandle())) { - // TODO(b/258629881): Display error dialog. + Binder.getCallingUserHandle(), destAddr)) { + TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, + Binder.getCallingUid(), callingPackage); sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED); return; } @@ -332,11 +385,13 @@ public class SmsController extends ISmsImplBase { if (getCallingPackage() != null) { callingPackage = getCallingPackage(); } + Rlog.d(LOG_TAG, "sendMultipartTextForSubscriber caller=" + callingPackage); // Check if user is associated with the subscription if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, - Binder.getCallingUserHandle())) { - // TODO(b/258629881): Display error dialog. + Binder.getCallingUserHandle(), destAddr)) { + TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, + Binder.getCallingUid(), callingPackage); sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED); return; } @@ -354,7 +409,7 @@ public class SmsController extends ISmsImplBase { messageId); } else { Rlog.e(LOG_TAG, "sendMultipartTextForSubscriber iccSmsIntMgr is null for" - + " Subscription: " + subId + " id: " + messageId); + + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId)); sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_ERROR_GENERIC_FAILURE); } } @@ -367,11 +422,13 @@ public class SmsController extends ISmsImplBase { if (callingPackage == null) { callingPackage = getCallingPackage(); } + Rlog.d(LOG_TAG, "sendMultipartTextForSubscriberWithOptions caller=" + callingPackage); // Check if user is associated with the subscription if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, - Binder.getCallingUserHandle())) { - // TODO(b/258629881): Display error dialog. + Binder.getCallingUserHandle(), destAddr)) { + TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, + Binder.getCallingUid(), callingPackage); sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED); return; } @@ -490,7 +547,7 @@ public class SmsController extends ISmsImplBase { // Don't show the SMS SIM Pick activity if it is not foreground. boolean isCallingProcessForeground = am != null && am.getUidImportance(Binder.getCallingUid()) - == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; + == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; if (!isCallingProcessForeground) { Rlog.d(LOG_TAG, "isSmsSimPickActivityNeeded: calling process not foreground. " + "Suppressing activity."); @@ -561,32 +618,19 @@ public class SmsController extends ISmsImplBase { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Override public int getPreferredSmsSubscription() { - int defaultSubId; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - // If there is a default, choose that one. - defaultSubId = SubscriptionManagerService.getInstance().getDefaultSmsSubId(); - } else { - // If there is a default, choose that one. - defaultSubId = SubscriptionController.getInstance().getDefaultSmsSubId(); - } + // If there is a default, choose that one. + int defaultSubId = SubscriptionManagerService.getInstance().getDefaultSmsSubId(); + if (SubscriptionManager.isValidSubscriptionId(defaultSubId)) { return defaultSubId; } // No default, if there is only one sub active, choose that as the "preferred" sub id. long token = Binder.clearCallingIdentity(); try { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - int[] activeSubs = SubscriptionManagerService.getInstance() - .getActiveSubIdList(true /*visibleOnly*/); - if (activeSubs.length == 1) { - return activeSubs[0]; - } - } else { - int[] activeSubs = SubscriptionController.getInstance() - .getActiveSubIdList(true /*visibleOnly*/); - if (activeSubs.length == 1) { - return activeSubs[0]; - } + int[] activeSubs = SubscriptionManagerService.getInstance() + .getActiveSubIdList(true /*visibleOnly*/); + if (activeSubs.length == 1) { + return activeSubs[0]; } } finally { Binder.restoreCallingIdentity(token); @@ -614,6 +658,8 @@ public class SmsController extends ISmsImplBase { throw new SecurityException("sendStoredText: Package " + callingPkg + "does not belong to " + Binder.getCallingUid()); } + Rlog.d(LOG_TAG, "sendStoredText caller=" + callingPkg); + if (iccSmsIntMgr != null) { iccSmsIntMgr.sendStoredText(callingPkg, callingAttributionTag, messageUri, scAddress, sentIntent, deliveryIntent); @@ -632,6 +678,8 @@ public class SmsController extends ISmsImplBase { throw new SecurityException("sendStoredMultipartText: Package " + callingPkg + " does not belong to " + Binder.getCallingUid()); } + Rlog.d(LOG_TAG, "sendStoredMultipartText caller=" + callingPkg); + if (iccSmsIntMgr != null) { iccSmsIntMgr.sendStoredMultipartText(callingPkg, callingAttributionTag, messageUri, scAddress, sentIntents, deliveryIntents); @@ -784,6 +832,58 @@ public class SmsController extends ISmsImplBase { return getPhone(subId).getAppSmsManager().createAppSpecificSmsToken(callingPkg, intent); } + @Override + public void setStorageMonitorMemoryStatusOverride(int subId, boolean isStorageAvailable) { + Phone phone = getPhone(subId); + Context context; + if (phone != null) { + context = phone.getContext(); + } else { + Rlog.e(LOG_TAG, "Phone Object is Null"); + return; + } + // If it doesn't have modify phone state permission + // a SecurityException will be thrown. + if (context.checkPermission(android.Manifest + .permission.MODIFY_PHONE_STATE, Binder.getCallingPid(), + Binder.getCallingUid()) != PERMISSION_GRANTED) { + throw new SecurityException( + "setStorageMonitorMemoryStatusOverride needs MODIFY_PHONE_STATE"); + } + final long identity = Binder.clearCallingIdentity(); + try { + phone.mSmsStorageMonitor.sendMemoryStatusOverride(isStorageAvailable); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void clearStorageMonitorMemoryStatusOverride(int subId) { + Phone phone = getPhone(subId); + Context context; + if (phone != null) { + context = phone.getContext(); + } else { + Rlog.e(LOG_TAG, "Phone Object is Null"); + return; + } + // If it doesn't have modify phone state permission + // a SecurityException will be thrown. + if (context.checkPermission(android.Manifest + .permission.MODIFY_PHONE_STATE, Binder.getCallingPid(), + Binder.getCallingUid()) != PERMISSION_GRANTED) { + throw new SecurityException( + "clearStorageMonitorMemoryStatusOverride needs MODIFY_PHONE_STATE"); + } + final long identity = Binder.clearCallingIdentity(); + try { + phone.mSmsStorageMonitor.clearMemoryStatusOverride(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public int checkSmsShortCodeDestination(int subId, String callingPackage, String callingFeatureId, String destAddress, String countryIso) { @@ -809,17 +909,20 @@ public class SmsController extends ISmsImplBase { public void sendVisualVoicemailSmsForSubscriber(String callingPackage, String callingAttributionTag, int subId, String number, int port, String text, PendingIntent sentIntent) { + Rlog.d(LOG_TAG, "sendVisualVoicemailSmsForSubscriber caller=" + callingPackage); + // Do not send non-emergency SMS in ECBM as it forces device to exit ECBM. if(getPhone(subId).isInEcm()) { Rlog.d(LOG_TAG, "sendVisualVoicemailSmsForSubscriber: Do not send non-emergency " - + "SMS in ECBM as it forces device to exit ECBM."); + + "SMS in ECBM as it forces device to exit ECBM."); return; } // Check if user is associated with the subscription if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, - Binder.getCallingUserHandle())) { - // TODO(b/258629881): Display error dialog. + Binder.getCallingUserHandle(), number)) { + TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, + Binder.getCallingUid(), callingPackage); sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED); return; } @@ -1004,7 +1107,7 @@ public class SmsController extends ISmsImplBase { } } else { Rlog.e(LOG_TAG, "getSmscAddressFromIccEfForSubscriber iccSmsIntMgr is null" - + " for Subscription: " + subId); + + " for Subscription: " + subId); return true; } diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java index 04927edd9078fdeae8f8ec17f1c27990aedf149c..d2dfcacdfb0721aee98305c0df627adfd4a46aed 100644 --- a/src/java/com/android/internal/telephony/SmsDispatchersController.java +++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java @@ -20,6 +20,8 @@ import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE; import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_NONE; import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_TEMPORARY; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; @@ -29,19 +31,30 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.AsyncResult; +import android.os.Binder; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.UserManager; import android.provider.Telephony.Sms; import android.provider.Telephony.Sms.Intents; +import android.telephony.Annotation.DisconnectCauses; +import android.telephony.DomainSelectionService; +import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.SmsManager; import android.telephony.SmsMessage; +import android.telephony.TelephonyManager; +import android.text.TextUtils; import com.android.ims.ImsManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.cdma.CdmaInboundSmsHandler; import com.android.internal.telephony.cdma.CdmaSMSDispatcher; +import com.android.internal.telephony.domainselection.DomainSelectionConnection; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; +import com.android.internal.telephony.domainselection.EmergencySmsDomainSelectionConnection; +import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection; import com.android.internal.telephony.gsm.GsmInboundSmsHandler; import com.android.internal.telephony.gsm.GsmSMSDispatcher; import com.android.telephony.Rlog; @@ -50,6 +63,8 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; /** * @@ -114,6 +129,195 @@ public class SmsDispatchersController extends Handler { private HashMap mDeliveryPendingMapFor3GPP2 = new HashMap<>(); + /** + * Testing interface for injecting mock DomainSelectionConnection and a flag to indicate + * whether the domain selection is supported. + */ + @VisibleForTesting + public interface DomainSelectionResolverProxy { + /** + * Returns a {@link DomainSelectionConnection} created using the specified + * context and callback. + * + * @param phone The {@link Phone} instance. + * @param selectorType The domain selector type to identify the domain selection connection. + * A {@link DomainSelectionService#SELECTOR_TYPE_SMS} is used for SMS. + * @param isEmergency A flag to indicate whether this connection is + * for an emergency SMS or not. + */ + @Nullable DomainSelectionConnection getDomainSelectionConnection(Phone phone, + @DomainSelectionService.SelectorType int selectorType, boolean isEmergency); + + /** + * Checks if the device supports the domain selection service to route the call / SMS / + * supplementary services to the appropriate domain. + * + * @return {@code true} if the domain selection is supported on the device, + * {@code false} otherwise. + */ + boolean isDomainSelectionSupported(); + } + + private DomainSelectionResolverProxy mDomainSelectionResolverProxy = + new DomainSelectionResolverProxy() { + @Override + @Nullable + public DomainSelectionConnection getDomainSelectionConnection(Phone phone, + @DomainSelectionService.SelectorType int selectorType, + boolean isEmergency) { + try { + return DomainSelectionResolver.getInstance().getDomainSelectionConnection( + phone, selectorType, isEmergency); + } catch (IllegalStateException e) { + // In general, DomainSelectionResolver is always initialized by TeleService, + // but if it's not initialized (like in unit tests), + // it returns null to perform the legacy behavior in this case. + return null; + } + } + + @Override + public boolean isDomainSelectionSupported() { + return DomainSelectionResolver.getInstance().isDomainSelectionSupported(); + } + }; + + /** Stores the sending SMS information for a pending request. */ + private class PendingRequest { + public static final int TYPE_DATA = 1; + public static final int TYPE_TEXT = 2; + public static final int TYPE_MULTIPART_TEXT = 3; + public static final int TYPE_RETRY_SMS = 4; + + public final int type; + public final SMSDispatcher.SmsTracker tracker; + public final String callingPackage; + public final String destAddr; + public final String scAddr; + public final ArrayList sentIntents; + public final ArrayList deliveryIntents; + public final boolean isForVvm; + // sendData specific + public final byte[] data; + public final int destPort; + // sendText / sendMultipartText specific + public final ArrayList texts; + public final Uri messageUri; + public final boolean persistMessage; + public final int priority; + public final boolean expectMore; + public final int validityPeriod; + public final long messageId; + public final boolean skipShortCodeCheck; + + PendingRequest(int type, SMSDispatcher.SmsTracker tracker, String callingPackage, + String destAddr, String scAddr, ArrayList sentIntents, + ArrayList deliveryIntents, boolean isForVvm, byte[] data, + int destPort, ArrayList texts, Uri messageUri, boolean persistMessage, + int priority, boolean expectMore, int validityPeriod, long messageId, + boolean skipShortCodeCheck) { + this.type = type; + this.tracker = tracker; + this.callingPackage = callingPackage; + this.destAddr = destAddr; + this.scAddr = scAddr; + this.sentIntents = sentIntents; + this.deliveryIntents = deliveryIntents; + this.isForVvm = isForVvm; + + this.data = data; + this.destPort = destPort; + + this.texts = texts; + this.messageUri = messageUri; + this.persistMessage = persistMessage; + this.priority = priority; + this.expectMore = expectMore; + this.validityPeriod = validityPeriod; + this.messageId = messageId; + this.skipShortCodeCheck = skipShortCodeCheck; + } + } + + /** + * Manages the {@link DomainSelectionConnection} instance and its related information. + */ + @VisibleForTesting + protected class DomainSelectionConnectionHolder + implements DomainSelectionConnection.DomainSelectionConnectionCallback { + private final boolean mEmergency; + // Manages the pending request while selecting a proper domain. + private final List mPendingRequests = new ArrayList<>(); + // Manages the domain selection connections: MO SMS or emergency SMS. + private DomainSelectionConnection mConnection; + + DomainSelectionConnectionHolder(boolean emergency) { + mEmergency = emergency; + } + + /** + * Returns a {@link DomainSelectionConnection} instance. + */ + public DomainSelectionConnection getConnection() { + return mConnection; + } + + /** + * Returns a list of {@link PendingRequest} that was added + * while the domain selection is performed. + */ + public List getPendingRequests() { + return mPendingRequests; + } + + /** + * Checks whether or not the domain selection is requested. + * If there is no pending request, the domain selection request is needed to + * select a proper domain for MO SMS. + */ + public boolean isDomainSelectionRequested() { + return !mPendingRequests.isEmpty(); + } + + /** + * Checks whether or not this holder is for an emergency SMS. + */ + public boolean isEmergency() { + return mEmergency; + } + + /** + * Clears all pending requests. + */ + public void clearAllRequests() { + mPendingRequests.clear(); + } + + /** + * Add a new pending request. + */ + public void addRequest(@NonNull PendingRequest request) { + mPendingRequests.add(request); + } + + /** + * Sets a {@link DomainSelectionConnection} instance. + */ + public void setConnection(DomainSelectionConnection connection) { + mConnection = connection; + } + + + @Override + public void onSelectionTerminated(@DisconnectCauses int cause) { + notifyDomainSelectionTerminated(this); + } + } + + /** Manages the domain selection connections: MO SMS or emergency SMS. */ + private DomainSelectionConnectionHolder mDscHolder; + private DomainSelectionConnectionHolder mEmergencyDscHolder; + /** * Puts a delivery pending tracker to the map based on the format. * @@ -129,6 +333,14 @@ public class SmsDispatchersController extends Handler { public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor, SmsUsageMonitor usageMonitor) { + this(phone, storageMonitor, usageMonitor, phone.getLooper()); + } + + @VisibleForTesting + public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor, + SmsUsageMonitor usageMonitor, Looper looper) { + super(looper); + Rlog.d(TAG, "SmsDispatchersController created"); mContext = phone.getContext(); @@ -141,9 +353,9 @@ public class SmsDispatchersController extends Handler { mImsSmsDispatcher = new ImsSmsDispatcher(phone, this, ImsManager::getConnector); mCdmaDispatcher = new CdmaSMSDispatcher(phone, this); mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), - storageMonitor, phone); + storageMonitor, phone, looper); mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), - storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher); + storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher, looper); mGsmDispatcher = new GsmSMSDispatcher(phone, this, mGsmInboundSmsHandler); SmsBroadcastUndelivered.initialize(phone.getContext(), mGsmInboundSmsHandler, mCdmaInboundSmsHandler); @@ -188,6 +400,9 @@ public class SmsDispatchersController extends Handler { mCdmaDispatcher.dispose(); mGsmInboundSmsHandler.dispose(); mCdmaInboundSmsHandler.dispose(); + // Cancels the domain selection request if it's still in progress. + finishDomainSelection(mDscHolder); + finishDomainSelection(mEmergencyDscHolder); } /** @@ -242,6 +457,21 @@ public class SmsDispatchersController extends Handler { } } + private String getSmscAddressFromUSIMWithPhoneIdentity(String callingPkg) { + final long identity = Binder.clearCallingIdentity(); + try { + IccSmsInterfaceManager iccSmsIntMgr = mPhone.getIccSmsInterfaceManager(); + if (iccSmsIntMgr != null) { + return iccSmsIntMgr.getSmscAddressFromIccEf(callingPkg); + } else { + Rlog.d(TAG, "getSmscAddressFromIccEf iccSmsIntMgr is null"); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + return null; + } + private void reevaluateTimerStatus() { long currentTime = System.currentTimeMillis(); @@ -390,7 +620,12 @@ public class SmsDispatchersController extends Handler { // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa. android.telephony.SmsMessage msg = android.telephony.SmsMessage.createFromPdu(pdu, format); - injectSmsPdu(msg, format, callback, false /* ignoreClass */, isOverIms); + injectSmsPdu(msg, format, callback, false /* ignoreClass */, isOverIms, 0 /* unused */); + } + + @VisibleForTesting + public void setImsSmsDispatcher(ImsSmsDispatcher imsSmsDispatcher) { + mImsSmsDispatcher = imsSmsDispatcher; } /** @@ -405,7 +640,7 @@ public class SmsDispatchersController extends Handler { */ @VisibleForTesting public void injectSmsPdu(SmsMessage msg, String format, SmsInjectionCallback callback, - boolean ignoreClass, boolean isOverIms) { + boolean ignoreClass, boolean isOverIms, int token) { Rlog.d(TAG, "SmsDispatchersController:injectSmsPdu"); try { if (msg == null) { @@ -427,7 +662,7 @@ public class SmsDispatchersController extends Handler { Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg + ", format=" + format + "to mGsmInboundSmsHandler"); mGsmInboundSmsHandler.sendMessage( - InboundSmsHandler.EVENT_INJECT_SMS, isOverIms ? 1 : 0, 0, ar); + InboundSmsHandler.EVENT_INJECT_SMS, isOverIms ? 1 : 0, token, ar); } else if (format.equals(SmsConstants.FORMAT_3GPP2)) { Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg + ", format=" + format + "to mCdmaInboundSmsHandler"); @@ -444,21 +679,61 @@ public class SmsDispatchersController extends Handler { } } + /** + * sets ImsManager object. + * + * @param imsManager holds a valid object or a null for setting + */ + public boolean setImsManager(ImsManager imsManager) { + if (mGsmInboundSmsHandler != null) { + mGsmInboundSmsHandler.setImsManager(imsManager); + return true; + } + return false; + } + /** * Retry the message along to the radio. * * @param tracker holds the SMS message to send */ public void sendRetrySms(SMSDispatcher.SmsTracker tracker) { - String oldFormat = tracker.mFormat; boolean retryUsingImsService = false; - if (!tracker.mUsesImsServiceForIms && mImsSmsDispatcher.isAvailable()) { - // If this tracker has not been handled by ImsSmsDispatcher yet and IMS Service is - // available now, retry this failed tracker using IMS Service. - retryUsingImsService = true; + if (!tracker.mUsesImsServiceForIms) { + if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) { + DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false); + + // If the DomainSelectionConnection is not available, + // fallback to the legacy implementation. + if (holder != null && holder.getConnection() != null) { + sendSmsUsingDomainSelection(holder, + new PendingRequest(PendingRequest.TYPE_RETRY_SMS, tracker, + null, null, null, null, null, false, null, 0, null, null, false, + 0, false, 0, 0L, false), + "sendRetrySms"); + return; + } + } + + if (mImsSmsDispatcher.isAvailable()) { + // If this tracker has not been handled by ImsSmsDispatcher yet and IMS Service is + // available now, retry this failed tracker using IMS Service. + retryUsingImsService = true; + } } + sendRetrySms(tracker, retryUsingImsService); + } + + /** + * Retry the message along to the radio. + * + * @param tracker holds the SMS message to send + * @param retryUsingImsService a flag to indicate whether the retry SMS can use the ImsService + */ + public void sendRetrySms(SMSDispatcher.SmsTracker tracker, boolean retryUsingImsService) { + String oldFormat = tracker.mFormat; // If retryUsingImsService is true, newFormat will be IMS SMS format. Otherwise, newFormat // will be based on voice technology. String newFormat = @@ -544,6 +819,23 @@ public class SmsDispatchersController extends Handler { dispatcher.sendSms(tracker); } + /** + * Memory Available Event + * @param result callback message + */ + public void reportSmsMemoryStatus(Message result) { + Rlog.d(TAG, "reportSmsMemoryStatus: "); + try { + mImsSmsDispatcher.onMemoryAvailable(); + AsyncResult.forMessage(result, null, null); + result.sendToTarget(); + } catch (Exception e) { + Rlog.e(TAG, "reportSmsMemoryStatus Failed ", e); + AsyncResult.forMessage(result, null, e); + result.sendToTarget(); + } + } + /** * SMS over IMS is supported if IMS is registered and SMS is supported on IMS. * @@ -590,6 +882,347 @@ public class SmsDispatchersController extends Handler { return (mCdmaDispatcher.getFormat().equals(format)); } + /** Sets a proxy interface for accessing the methods of {@link DomainSelectionResolver}. */ + @VisibleForTesting + public void setDomainSelectionResolverProxy(@NonNull DomainSelectionResolverProxy proxy) { + mDomainSelectionResolverProxy = proxy; + } + + /** + * Determines whether or not to use CDMA format for MO SMS when the domain selection uses. + * If the domain is {@link NetworkRegistrationInfo#DOMAIN_PS}, then format is based on + * IMS SMS format, otherwise format is based on current phone type. + * + * @return {@code true} if CDMA format should be used for MO SMS, {@code false} otherwise. + */ + private boolean isCdmaMo(@NetworkRegistrationInfo.Domain int domain) { + if (domain != NetworkRegistrationInfo.DOMAIN_PS) { + // IMS is not registered, use voice technology to determine SMS format. + return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()); + } + // IMS is registered with SMS support + return isCdmaFormat(mImsSmsDispatcher.getFormat()); + } + + /** + * Returns a {@link DomainSelectionConnectionHolder} according to the flag specified. + * + * @param emergency The flag to indicate that the domain selection is for an emergency SMS. + * @return A {@link DomainSelectionConnectionHolder} instance or null. + */ + @VisibleForTesting + @Nullable + protected DomainSelectionConnectionHolder getDomainSelectionConnectionHolder( + boolean emergency) { + return emergency ? mEmergencyDscHolder : mDscHolder; + } + + /** + * Returns a {@link DomainSelectionConnectionHolder} if the domain selection supports, + * return null otherwise. + * + * @param emergency The flag to indicate that the domain selection is for an emergency SMS. + * @return A {@link DomainSelectionConnectionHolder} that grabs the + * {@link DomainSelectionConnection} and its related information to use the domain + * selection architecture. + */ + private DomainSelectionConnectionHolder getDomainSelectionConnection(boolean emergency) { + DomainSelectionConnectionHolder holder = getDomainSelectionConnectionHolder(emergency); + DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null; + boolean created = false; + + if (connection == null) { + connection = mDomainSelectionResolverProxy.getDomainSelectionConnection( + mPhone, DomainSelectionService.SELECTOR_TYPE_SMS, emergency); + + if (connection == null) { + // Domain selection architecture is not supported. + // Use the legacy architecture. + return null; + } + + created = true; + } + + if (holder == null) { + holder = new DomainSelectionConnectionHolder(emergency); + + if (emergency) { + mEmergencyDscHolder = holder; + } else { + mDscHolder = holder; + } + } + + holder.setConnection(connection); + + return holder; + } + + /** + * Requests the domain selection for MO SMS. + * + * @param holder The {@link DomainSelectionConnectionHolder} that contains the + * {@link DomainSelectionConnection} and its related information. + */ + private void requestDomainSelection(@NonNull DomainSelectionConnectionHolder holder) { + DomainSelectionService.SelectionAttributes attr = + new DomainSelectionService.SelectionAttributes.Builder(mPhone.getPhoneId(), + mPhone.getSubId(), DomainSelectionService.SELECTOR_TYPE_SMS) + .setEmergency(holder.isEmergency()) + .build(); + + if (holder.isEmergency()) { + EmergencySmsDomainSelectionConnection emergencyConnection = + (EmergencySmsDomainSelectionConnection) holder.getConnection(); + CompletableFuture future = + emergencyConnection.requestDomainSelection(attr, holder); + future.thenAcceptAsync((domain) -> { + if (VDBG) { + logd("requestDomainSelection(emergency): domain=" + + DomainSelectionService.getDomainName(domain)); + } + sendAllPendingRequests(holder, domain); + finishDomainSelection(holder); + }, this::post); + } else { + SmsDomainSelectionConnection connection = + (SmsDomainSelectionConnection) holder.getConnection(); + CompletableFuture future = connection.requestDomainSelection(attr, holder); + future.thenAcceptAsync((domain) -> { + if (VDBG) { + logd("requestDomainSelection: domain=" + + DomainSelectionService.getDomainName(domain)); + } + sendAllPendingRequests(holder, domain); + finishDomainSelection(holder); + }, this::post); + } + } + + /** + * Sends a SMS after selecting the domain via the domain selection service. + * + * @param holder The {@link DomainSelectionConnectionHolder} that contains the + * {@link DomainSelectionConnection} and its related information. + * @param request The {@link PendingRequest} that stores the SMS request + * (data, text, multipart text) to be sent. + * @param logTag The log tag to display which method called this method. + */ + private void sendSmsUsingDomainSelection(@NonNull DomainSelectionConnectionHolder holder, + @NonNull PendingRequest request, @NonNull String logTag) { + boolean isDomainSelectionRequested = holder.isDomainSelectionRequested(); + // The domain selection is in progress so waits for the result of + // the domain selection by adding this request to the pending list. + holder.addRequest(request); + + if (!isDomainSelectionRequested) { + if (VDBG) { + logd("requestDomainSelection: " + logTag); + } + requestDomainSelection(holder); + } + } + + /** + * Finishes the domain selection for MO SMS. + * + * @param holder The {@link DomainSelectionConnectionHolder} object that is being finished. + */ + private void finishDomainSelection(DomainSelectionConnectionHolder holder) { + DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null; + + if (connection != null) { + // After this method is called, the domain selection service will clean up + // its resources and finish the procedure that are related to the current domain + // selection request. + connection.finishSelection(); + } + + if (holder != null) { + final List pendingRequests = holder.getPendingRequests(); + + logd("finishDomainSelection: pendingRequests=" + pendingRequests.size()); + + for (PendingRequest r : pendingRequests) { + triggerSentIntentForFailure(r.sentIntents); + } + + holder.clearAllRequests(); + holder.setConnection(null); + } + } + + /** + * Notifies the application that MO SMS is not sent by the error of domain selection. + * + * @param holder The {@link DomainSelectionConnectionHolder} object that is being terminated. + */ + private void notifyDomainSelectionTerminated(@NonNull DomainSelectionConnectionHolder holder) { + final List pendingRequests = holder.getPendingRequests(); + + logd("notifyDomainSelectionTerminated: pendingRequests=" + pendingRequests.size()); + + for (PendingRequest r : pendingRequests) { + triggerSentIntentForFailure(r.sentIntents); + } + + holder.setConnection(null); + holder.clearAllRequests(); + } + + /** + * Sends all pending requests for MO SMS. + * + * @param holder The {@link DomainSelectionConnectionHolder} object that all the pending + * requests are handled. + * @param domain The domain where the SMS is being sent, which can be one of the following: + * - {@link NetworkRegistrationInfo#DOMAIN_PS} + * - {@link NetworkRegistrationInfo#DOMAIN_CS} + */ + private void sendAllPendingRequests(@NonNull DomainSelectionConnectionHolder holder, + @NetworkRegistrationInfo.Domain int domain) { + final List pendingRequests = holder.getPendingRequests(); + + if (VDBG) { + logd("sendAllPendingRequests: domain=" + DomainSelectionService.getDomainName(domain) + + ", size=" + pendingRequests.size()); + } + + for (PendingRequest r : pendingRequests) { + switch (r.type) { + case PendingRequest.TYPE_DATA: + sendData(domain, r); + break; + case PendingRequest.TYPE_TEXT: + sendText(domain, r); + break; + case PendingRequest.TYPE_MULTIPART_TEXT: + sendMultipartText(domain, r); + break; + case PendingRequest.TYPE_RETRY_SMS: + sendRetrySms(r.tracker, (domain == NetworkRegistrationInfo.DOMAIN_PS)); + break; + default: + // Not reachable. + break; + } + } + + holder.clearAllRequests(); + } + + /** + * Sends a data based SMS to a specific application port. + * + * @param domain The domain where the SMS is being sent, which can be one of the following: + * - {@link NetworkRegistrationInfo#DOMAIN_PS} + * - {@link NetworkRegistrationInfo#DOMAIN_CS} + * @param request The pending request for MO SMS. + */ + private void sendData(@NetworkRegistrationInfo.Domain int domain, + @NonNull PendingRequest request) { + if (domain == NetworkRegistrationInfo.DOMAIN_PS) { + mImsSmsDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr, + request.destPort, request.data, request.sentIntents.get(0), + request.deliveryIntents.get(0), request.isForVvm); + } else if (isCdmaMo(domain)) { + mCdmaDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr, + request.destPort, request.data, request.sentIntents.get(0), + request.deliveryIntents.get(0), request.isForVvm); + } else { + mGsmDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr, + request.destPort, request.data, request.sentIntents.get(0), + request.deliveryIntents.get(0), request.isForVvm); + } + } + + /** + * Sends a text based SMS. + * + * @param domain The domain where the SMS is being sent, which can be one of the following: + * - {@link NetworkRegistrationInfo#DOMAIN_PS} + * - {@link NetworkRegistrationInfo#DOMAIN_CS} + * @param request The pending request for MO SMS. + */ + private void sendText(@NetworkRegistrationInfo.Domain int domain, + @NonNull PendingRequest request) { + if (domain == NetworkRegistrationInfo.DOMAIN_PS) { + mImsSmsDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0), + request.sentIntents.get(0), request.deliveryIntents.get(0), + request.messageUri, request.callingPackage, request.persistMessage, + request.priority, false /*request.expectMore*/, request.validityPeriod, + request.isForVvm, request.messageId, request.skipShortCodeCheck); + } else { + if (isCdmaMo(domain)) { + mCdmaDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0), + request.sentIntents.get(0), request.deliveryIntents.get(0), + request.messageUri, request.callingPackage, request.persistMessage, + request.priority, request.expectMore, request.validityPeriod, + request.isForVvm, request.messageId, request.skipShortCodeCheck); + } else { + mGsmDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0), + request.sentIntents.get(0), request.deliveryIntents.get(0), + request.messageUri, request.callingPackage, request.persistMessage, + request.priority, request.expectMore, request.validityPeriod, + request.isForVvm, request.messageId, request.skipShortCodeCheck); + } + } + } + + /** + * Sends a multi-part text based SMS. + * + * @param domain The domain where the SMS is being sent, which can be one of the following: + * - {@link NetworkRegistrationInfo#DOMAIN_PS} + * - {@link NetworkRegistrationInfo#DOMAIN_CS} + * @param request The pending request for MO SMS. + */ + private void sendMultipartText(@NetworkRegistrationInfo.Domain int domain, + @NonNull PendingRequest request) { + if (domain == NetworkRegistrationInfo.DOMAIN_PS) { + mImsSmsDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts, + request.sentIntents, request.deliveryIntents, request.messageUri, + request.callingPackage, request.persistMessage, request.priority, + false /*request.expectMore*/, request.validityPeriod, request.messageId); + } else { + if (isCdmaMo(domain)) { + mCdmaDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts, + request.sentIntents, request.deliveryIntents, request.messageUri, + request.callingPackage, request.persistMessage, request.priority, + request.expectMore, request.validityPeriod, request.messageId); + } else { + mGsmDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts, + request.sentIntents, request.deliveryIntents, request.messageUri, + request.callingPackage, request.persistMessage, request.priority, + request.expectMore, request.validityPeriod, request.messageId); + } + } + } + + private void triggerSentIntentForFailure(@NonNull PendingIntent sentIntent) { + try { + sentIntent.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE); + } catch (CanceledException e) { + logd("Intent has been canceled!"); + } + } + + private void triggerSentIntentForFailure(@NonNull List sentIntents) { + for (PendingIntent sentIntent : sentIntents) { + triggerSentIntentForFailure(sentIntent); + } + } + + /** + * Creates an ArrayList object from any object. + */ + private static ArrayList asArrayList(T object) { + ArrayList list = new ArrayList<>(); + list.add(object); + return list; + } + /** * Send a data based SMS to a specific application port. * @@ -671,6 +1304,26 @@ public class SmsDispatchersController extends Handler { */ protected void sendData(String callingPackage, String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) { + if (TextUtils.isEmpty(scAddr)) { + scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPackage); + } + + if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) { + DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false); + + // If the DomainSelectionConnection is not available, + // fallback to the legacy implementation. + if (holder != null && holder.getConnection() != null) { + sendSmsUsingDomainSelection(holder, + new PendingRequest(PendingRequest.TYPE_DATA, null, callingPackage, + destAddr, scAddr, asArrayList(sentIntent), + asArrayList(deliveryIntent), isForVvm, data, destPort, null, null, + false, 0, false, 0, 0L, false), + "sendData"); + return; + } + } + if (mImsSmsDispatcher.isAvailable()) { mImsSmsDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent, deliveryIntent, isForVvm); @@ -784,19 +1437,148 @@ public class SmsDispatchersController extends Handler { PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage, int priority, boolean expectMore, int validityPeriod, boolean isForVvm, long messageId) { + sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg, + persistMessage, priority, expectMore, validityPeriod, isForVvm, messageId, false); + } + + /** + * Send a text based SMS. + * + * @param destAddr the address to send the message to + * @param scAddr is the service center address or null to use + * the current default SMSC + * @param text the body of the message to send + * @param sentIntent if not NULL this PendingIntent is + * broadcast when the message is successfully sent, or failed. + * The result code will be Activity.RESULT_OK for success, + * or one of these errors:
+ * SmsManager.RESULT_ERROR_GENERIC_FAILURE
+ * SmsManager.RESULT_ERROR_RADIO_OFF
+ * SmsManager.RESULT_ERROR_NULL_PDU
+ * SmsManager.RESULT_ERROR_NO_SERVICE
+ * SmsManager.RESULT_ERROR_LIMIT_EXCEEDED
+ * SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE
+ * SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED
+ * SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED
+ * SmsManager.RESULT_RADIO_NOT_AVAILABLE
+ * SmsManager.RESULT_NETWORK_REJECT
+ * SmsManager.RESULT_INVALID_ARGUMENTS
+ * SmsManager.RESULT_INVALID_STATE
+ * SmsManager.RESULT_NO_MEMORY
+ * SmsManager.RESULT_INVALID_SMS_FORMAT
+ * SmsManager.RESULT_SYSTEM_ERROR
+ * SmsManager.RESULT_MODEM_ERROR
+ * SmsManager.RESULT_NETWORK_ERROR
+ * SmsManager.RESULT_ENCODING_ERROR
+ * SmsManager.RESULT_INVALID_SMSC_ADDRESS
+ * SmsManager.RESULT_OPERATION_NOT_ALLOWED
+ * SmsManager.RESULT_INTERNAL_ERROR
+ * SmsManager.RESULT_NO_RESOURCES
+ * SmsManager.RESULT_CANCELLED
+ * SmsManager.RESULT_REQUEST_NOT_SUPPORTED
+ * SmsManager.RESULT_NO_BLUETOOTH_SERVICE
+ * SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS
+ * SmsManager.RESULT_BLUETOOTH_DISCONNECTED
+ * SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING
+ * SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY
+ * SmsManager.RESULT_SMS_SEND_RETRY_FAILED
+ * SmsManager.RESULT_REMOTE_EXCEPTION
+ * SmsManager.RESULT_NO_DEFAULT_SMS_APP
+ * SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE
+ * SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY
+ * SmsManager.RESULT_RIL_NETWORK_REJECT
+ * SmsManager.RESULT_RIL_INVALID_STATE
+ * SmsManager.RESULT_RIL_INVALID_ARGUMENTS
+ * SmsManager.RESULT_RIL_NO_MEMORY
+ * SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED
+ * SmsManager.RESULT_RIL_INVALID_SMS_FORMAT
+ * SmsManager.RESULT_RIL_SYSTEM_ERR
+ * SmsManager.RESULT_RIL_ENCODING_ERR
+ * SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS
+ * SmsManager.RESULT_RIL_MODEM_ERR
+ * SmsManager.RESULT_RIL_NETWORK_ERR
+ * SmsManager.RESULT_RIL_INTERNAL_ERR
+ * SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED
+ * SmsManager.RESULT_RIL_INVALID_MODEM_STATE
+ * SmsManager.RESULT_RIL_NETWORK_NOT_READY
+ * SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED
+ * SmsManager.RESULT_RIL_NO_RESOURCES
+ * SmsManager.RESULT_RIL_CANCELLED
+ * SmsManager.RESULT_RIL_SIM_ABSENT
+ * SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED
+ * SmsManager.RESULT_RIL_ACCESS_BARRED
+ * SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL
+ * For SmsManager.RESULT_ERROR_GENERIC_FAILURE or any of the RESULT_RIL errors, + * the sentIntent may include the extra "errorCode" containing a radio technology specific + * value, generally only useful for troubleshooting.
+ * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this PendingIntent is + * broadcast when the message is delivered to the recipient. The + * @param messageUri optional URI of the message if it is already stored in the system + * @param callingPkg the calling package name + * @param persistMessage whether to save the sent message into SMS DB for a + * non-default SMS app. + * @param priority Priority level of the message + * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 + * --------------------------------- + * PRIORITY | Level of Priority + * --------------------------------- + * '00' | Normal + * '01' | Interactive + * '10' | Urgent + * '11' | Emergency + * ---------------------------------- + * Any Other values included Negative considered as Invalid Priority Indicator of the message. + * @param expectMore is a boolean to indicate the sending messages through same link or not. + * @param validityPeriod Validity Period of the message in mins. + * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. + * Validity Period(Minimum) -> 5 mins + * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). + * Any Other values included Negative considered as Invalid Validity Period of the message. + * @param skipShortCodeCheck Skip check for short code type destination address. + */ + public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, + PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage, + int priority, boolean expectMore, int validityPeriod, boolean isForVvm, + long messageId, boolean skipShortCodeCheck) { + if (TextUtils.isEmpty(scAddr)) { + scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg); + } + + if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + boolean isEmergency = tm.isEmergencyNumber(destAddr); + DomainSelectionConnectionHolder holder = getDomainSelectionConnection(isEmergency); + + // If the DomainSelectionConnection is not available, + // fallback to the legacy implementation. + if (holder != null && holder.getConnection() != null) { + sendSmsUsingDomainSelection(holder, + new PendingRequest(PendingRequest.TYPE_TEXT, null, callingPkg, + destAddr, scAddr, asArrayList(sentIntent), + asArrayList(deliveryIntent), isForVvm, null, 0, asArrayList(text), + messageUri, persistMessage, priority, expectMore, validityPeriod, + messageId, skipShortCodeCheck), + "sendText"); + return; + } + } + if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) { mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage, priority, false /*expectMore*/, - validityPeriod, isForVvm, messageId); + validityPeriod, isForVvm, messageId, skipShortCodeCheck); } else { if (isCdmaMo()) { mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage, priority, expectMore, - validityPeriod, isForVvm, messageId); + validityPeriod, isForVvm, messageId, skipShortCodeCheck); } else { mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage, priority, expectMore, - validityPeriod, isForVvm, messageId); + validityPeriod, isForVvm, messageId, skipShortCodeCheck); } } } @@ -910,6 +1692,26 @@ public class SmsDispatchersController extends Handler { ArrayList deliveryIntents, Uri messageUri, String callingPkg, boolean persistMessage, int priority, boolean expectMore, int validityPeriod, long messageId) { + if (TextUtils.isEmpty(scAddr)) { + scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg); + } + + if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) { + DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false); + + // If the DomainSelectionConnection is not available, + // fallback to the legacy implementation. + if (holder != null && holder.getConnection() != null) { + sendSmsUsingDomainSelection(holder, + new PendingRequest(PendingRequest.TYPE_MULTIPART_TEXT, null, + callingPkg, destAddr, scAddr, sentIntents, deliveryIntents, false, + null, 0, parts, messageUri, persistMessage, priority, expectMore, + validityPeriod, messageId, false), + "sendMultipartText"); + return; + } + } + if (mImsSmsDispatcher.isAvailable()) { mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage, priority, @@ -1066,4 +1868,8 @@ public class SmsDispatchersController extends Handler { private void logd(String msg) { Rlog.d(TAG, msg); } + + private void logi(String s) { + Rlog.i(TAG + " [" + mPhone.getPhoneId() + "]", s); + } } diff --git a/src/java/com/android/internal/telephony/SmsPermissions.java b/src/java/com/android/internal/telephony/SmsPermissions.java index 44751acf9dad100f4036209a8772ba3f8991dbb7..529eea02c11744b7559ce2c24fed6ef1e0b6ea92 100644 --- a/src/java/com/android/internal/telephony/SmsPermissions.java +++ b/src/java/com/android/internal/telephony/SmsPermissions.java @@ -23,9 +23,11 @@ import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Build; +import android.os.UserHandle; import android.service.carrier.CarrierMessagingService; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; /** @@ -132,10 +134,19 @@ public class SmsPermissions { */ public boolean checkCallingOrSelfCanGetSmscAddress(String callingPackage, String message) { // Allow it to the default SMS app always. - if (!isCallerDefaultSmsPackage(callingPackage)) { - TelephonyPermissions - .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege( - mContext, mPhone.getSubId(), message); + boolean isDefaultSmsPackage; + int callerUid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { + isDefaultSmsPackage = isCallerDefaultSmsPackage(callingPackage, callerUid); + } finally { + Binder.restoreCallingIdentity(identity); + } + if (!isDefaultSmsPackage) { + Rlog.d(LOG_TAG, "Caller is not a default SMS application"); + TelephonyPermissions. + enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege( + mContext, mPhone.getSubId(), message); } return true; } @@ -151,33 +162,49 @@ public class SmsPermissions { */ public boolean checkCallingOrSelfCanSetSmscAddress(String callingPackage, String message) { // Allow it to the default SMS app always. - if (!isCallerDefaultSmsPackage(callingPackage)) { + boolean isDefaultSmsPackage; + int callerUid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { + isDefaultSmsPackage = isCallerDefaultSmsPackage(callingPackage, callerUid); + } finally { + Binder.restoreCallingIdentity(identity); + } + if (!isDefaultSmsPackage) { + Rlog.d(LOG_TAG, "Caller is not a default SMS application"); // Allow it with MODIFY_PHONE_STATE or Carrier Privileges - TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege( - mContext, mPhone.getSubId(), message); + TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mContext, + mPhone.getSubId(), message); } return true; } /** Check if a package is default SMS app. */ @VisibleForTesting - public boolean isCallerDefaultSmsPackage(String packageName) { - if (packageNameMatchesCallingUid(packageName)) { - return SmsApplication.isDefaultSmsApplication(mContext, packageName); + public boolean isCallerDefaultSmsPackage(String packageName, int callerUid) { + if (packageNameMatchesCallingUid(packageName, callerUid)) { + UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, + mPhone.getSubId()); + return SmsApplication.isDefaultSmsApplicationAsUser(mContext, packageName, userHandle); } return false; } + @VisibleForTesting + public boolean packageNameMatchesCallingUid(String packageName) { + return packageNameMatchesCallingUid(packageName, Binder.getCallingUid()); + } + /** * Check if the passed in packageName belongs to the calling uid. * @param packageName name of the package to check * @return true if package belongs to calling uid, false otherwise */ @VisibleForTesting - public boolean packageNameMatchesCallingUid(String packageName) { + public boolean packageNameMatchesCallingUid(String packageName, int callerUid) { try { ((AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE)) - .checkPackage(Binder.getCallingUid(), packageName); + .checkPackage(callerUid, packageName); // If checkPackage doesn't throw an exception then we are the given package return true; } catch (SecurityException e) { diff --git a/src/java/com/android/internal/telephony/SmsStorageMonitor.java b/src/java/com/android/internal/telephony/SmsStorageMonitor.java old mode 100755 new mode 100644 index 2375c732ec3a8fefdc0e991d3de58a3bebde3901..2736f7a8c751560e47a4fe3ce45f8035dfaf87ac --- a/src/java/com/android/internal/telephony/SmsStorageMonitor.java +++ b/src/java/com/android/internal/telephony/SmsStorageMonitor.java @@ -18,18 +18,22 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.os.AsyncResult; import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.PowerManager; +import android.os.UserHandle; import android.provider.Telephony.Sms.Intents; import android.telephony.SubscriptionManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; /** @@ -40,7 +44,7 @@ import com.android.telephony.Rlog; * dual-mode devices that require support for both 3GPP and 3GPP2 format messages. */ public class SmsStorageMonitor extends Handler { - private static final String TAG = "SmsStorageMonitor"; + private static final String TAG = "SmsStorageMonitor1"; /** Maximum number of times to retry memory status reporting */ private static final int MAX_RETRIES = 1; @@ -83,6 +87,10 @@ public class SmsStorageMonitor extends Handler { final CommandsInterface mCi; boolean mStorageAvailable = true; + boolean mInitialStorageAvailableStatus = true; + + private boolean mMemoryStatusOverrideFlag = false; + /** * Hold the wake lock for 5 seconds, which should be enough time for * any receiver(s) to grab its own wake lock. @@ -111,6 +119,31 @@ public class SmsStorageMonitor extends Handler { mContext.registerReceiver(mResultReceiver, filter); } + /** + * Overriding of the Memory Status by the TestApi and send the event to Handler to test + * the RP-SMMA feature + * @param isStorageAvailable boolean value specifies the MemoryStatus to be + * sent to Handler + */ + public void sendMemoryStatusOverride(boolean isStorageAvailable) { + if (!mMemoryStatusOverrideFlag) { + mInitialStorageAvailableStatus = mStorageAvailable; + mMemoryStatusOverrideFlag = true; + } + mStorageAvailable = isStorageAvailable; + if (isStorageAvailable) { + sendMessage(obtainMessage(EVENT_REPORT_MEMORY_STATUS)); + } + } + + /** + * reset Memory Status change made by {@link #sendMemoryStatusOverride} + */ + public void clearMemoryStatusOverride() { + mStorageAvailable = mInitialStorageAvailableStatus; + mMemoryStatusOverrideFlag = false; + } + @VisibleForTesting public void setMaxRetries(int maxCount) { mMaxRetryCount = maxCount; @@ -209,6 +242,18 @@ public class SmsStorageMonitor extends Handler { private void sendMemoryStatusReport(boolean isAvailable) { mIsWaitingResponse = true; + Resources r = mContext.getResources(); + if (r.getBoolean(com.android.internal.R.bool.config_smma_notification_supported_over_ims)) { + IccSmsInterfaceManager smsIfcMngr = mPhone.getIccSmsInterfaceManager(); + if (smsIfcMngr != null) { + Rlog.d(TAG, "sendMemoryStatusReport: smsIfcMngr is available"); + if (smsIfcMngr.mDispatchersController.isIms() && isAvailable) { + smsIfcMngr.mDispatchersController.reportSmsMemoryStatus( + obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); + return; + } + } + } mCi.reportSmsMemoryStatus(isAvailable, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); } @@ -223,9 +268,14 @@ public class SmsStorageMonitor extends Handler { * that SIM storage for SMS messages is full. */ private void handleIccFull() { + UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, + mPhone.getSubId()); + ComponentName componentName = SmsApplication.getDefaultSimFullApplicationAsUser(mContext, + false, userHandle); + // broadcast SIM_FULL intent Intent intent = new Intent(Intents.SIM_FULL_ACTION); - intent.setComponent(SmsApplication.getDefaultSimFullApplication(mContext, false)); + intent.setComponent(componentName); mWakeLock.acquire(WAKE_LOCK_TIMEOUT); SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS); diff --git a/src/java/com/android/internal/telephony/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java index 8bcdc07b55a2a7a0b9e1366505bdfc4eac67b209..8e4ac60419b948e0d3fd6e1df3f70d6881aa7898 100644 --- a/src/java/com/android/internal/telephony/SmsUsageMonitor.java +++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java @@ -43,6 +43,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -73,6 +74,8 @@ public class SmsUsageMonitor { private static final String SHORT_CODE_PATH = "/data/misc/sms/codes"; + private static final String SHORT_CODE_VERSION_PATH = "/data/misc/sms/metadata/version"; + /** Default checking period for SMS sent without user permission. */ private static final int DEFAULT_SMS_CHECK_PERIOD = 60000; // 1 minute @@ -128,6 +131,8 @@ public class SmsUsageMonitor { /** Last modified time for pattern file */ private long mPatternFileLastModified = 0; + private int mPatternFileVersion = -1; + private RoleManager mRoleManager; /** Directory for per-app SMS permission XML file. */ @@ -415,9 +420,11 @@ public class SmsUsageMonitor { if (mPatternFile.exists()) { if (DBG) Rlog.d(TAG, "Loading SMS Short Code patterns from file"); mCurrentPatternMatcher = getPatternMatcherFromFile(countryIso); + mPatternFileVersion = getPatternFileVersionFromFile(); } else { if (DBG) Rlog.d(TAG, "Loading SMS Short Code patterns from resource"); mCurrentPatternMatcher = getPatternMatcherFromResource(countryIso); + mPatternFileVersion = -1; } mCurrentCountry = countryIso; } @@ -655,6 +662,37 @@ public class SmsUsageMonitor { return false; } + private int getPatternFileVersionFromFile() { + File versionFile = new File(SHORT_CODE_VERSION_PATH); + if (versionFile.exists()) { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(versionFile)); + String version = reader.readLine(); + if (version != null) { + return Integer.parseInt(version); + } + } catch (IOException e) { + Rlog.e(TAG, "File reader exception reading short code " + + "pattern file version", e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + Rlog.e(TAG, "File reader exception closing short code " + + "pattern file version reader", e); + } + } + } + return -1; + } + + public int getShortCodeXmlFileVersion() { + return mPatternFileVersion; + } + private static void log(String msg) { Rlog.d(TAG, msg); } diff --git a/src/java/com/android/internal/telephony/SrvccConnection.java b/src/java/com/android/internal/telephony/SrvccConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..f25a15cae2d44b8736a7edbb84989355272bbda7 --- /dev/null +++ b/src/java/com/android/internal/telephony/SrvccConnection.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DIALING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_HOLDING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING_SETUP; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_WAITING; + +import android.net.Uri; +import android.telephony.Annotation.PreciseCallStates; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsStreamMediaProfile; +import android.text.TextUtils; + +import com.android.ims.internal.ConferenceParticipant; +import com.android.internal.telephony.imsphone.ImsPhoneConnection; +import com.android.telephony.Rlog; + +/** + * Connection information for SRVCC + */ +public class SrvccConnection { + private static final String TAG = "SrvccConnection"; + + public static final int CALL_TYPE_NORMAL = 0; + public static final int CALL_TYPE_EMERGENCY = 1; + + public static final int SUBSTATE_NONE = 0; + /** Pre-alerting state. Applicable for MT calls only */ + public static final int SUBSTATE_PREALERTING = 1; + + public static final int TONE_NONE = 0; + public static final int TONE_LOCAL = 1; + public static final int TONE_NETWORK = 2; + + /** Values are CALL_TYPE_ */ + private int mType = CALL_TYPE_NORMAL; + + /** Values are Call.State */ + private Call.State mState; + + /** Values are SUBSTATE_ */ + private int mSubstate = SUBSTATE_NONE; + + /** Values are TONE_ */ + private int mRingbackToneType = TONE_NONE; + + /** true if it is a multi-party call */ + private boolean mIsMpty = false; + + /** true if it is a mobile terminated call */ + private boolean mIsMT; + + /** Remote party nummber */ + private String mNumber; + + /** Values are PhoneConstants.PRESENTATION_ */ + private int mNumPresentation; + + /** Remote party name */ + private String mName; + + /** Values are PhoneConstants.PRESENTATION_ */ + private int mNamePresentation; + + public SrvccConnection(ImsCallProfile profile, + ImsPhoneConnection c, @PreciseCallStates int preciseCallState) { + mState = toCallState(preciseCallState); + if (mState == Call.State.ALERTING) { + mRingbackToneType = isLocalTone(profile) ? TONE_LOCAL : TONE_NETWORK; + } + if (preciseCallState == PRECISE_CALL_STATE_INCOMING_SETUP) { + mSubstate = SUBSTATE_PREALERTING; + } + + if (c == null) { + initialize(profile); + } else { + initialize(c); + } + } + + public SrvccConnection(ConferenceParticipant cp, @PreciseCallStates int preciseCallState) { + Rlog.d(TAG, "initialize with ConferenceParticipant"); + mState = toCallState(preciseCallState); + mIsMT = cp.getCallDirection() == android.telecom.Call.Details.DIRECTION_INCOMING; + mNumber = getParticipantAddress(cp.getHandle()); + mNumPresentation = cp.getParticipantPresentation(); + if (mNumPresentation == PhoneConstants.PRESENTATION_RESTRICTED) { + mNumber = ""; + } + mName = cp.getDisplayName(); + if (!TextUtils.isEmpty(mName)) { + mNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; + } else { + mNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; + } + mIsMpty = true; + } + + private static String getParticipantAddress(Uri address) { + if (address == null) { + return null; + } + + String number = address.getSchemeSpecificPart(); + if (TextUtils.isEmpty(number)) { + return null; + } + + String[] numberParts = number.split("[@;:]"); + if (numberParts.length == 0) return null; + + return numberParts[0]; + } + + // MT call in alerting or prealerting state + private void initialize(ImsCallProfile profile) { + Rlog.d(TAG, "initialize with ImsCallProfile"); + mIsMT = true; + mNumber = profile.getCallExtra(ImsCallProfile.EXTRA_OI); + mName = profile.getCallExtra(ImsCallProfile.EXTRA_CNA); + mNumPresentation = ImsCallProfile.OIRToPresentation( + profile.getCallExtraInt(ImsCallProfile.EXTRA_OIR)); + mNamePresentation = ImsCallProfile.OIRToPresentation( + profile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); + } + + private void initialize(ImsPhoneConnection c) { + Rlog.d(TAG, "initialize with ImsPhoneConnection"); + if (c.isEmergencyCall()) { + mType = CALL_TYPE_EMERGENCY; + } + mIsMT = c.isIncoming(); + mNumber = c.getAddress(); + mNumPresentation = c.getNumberPresentation(); + mName = c.getCnapName(); + mNamePresentation = c.getCnapNamePresentation(); + } + + private boolean isLocalTone(ImsCallProfile profile) { + if (profile == null) return false; + + ImsStreamMediaProfile mediaProfile = profile.getMediaProfile(); + if (mediaProfile == null) return false; + + boolean shouldPlayRingback = + (mediaProfile.getAudioDirection() == ImsStreamMediaProfile.DIRECTION_INACTIVE) + ? true : false; + return shouldPlayRingback; + } + + private static Call.State toCallState(int preciseCallState) { + switch (preciseCallState) { + case PRECISE_CALL_STATE_ACTIVE: return Call.State.ACTIVE; + case PRECISE_CALL_STATE_HOLDING: return Call.State.HOLDING; + case PRECISE_CALL_STATE_DIALING: return Call.State.DIALING; + case PRECISE_CALL_STATE_ALERTING: return Call.State.ALERTING; + case PRECISE_CALL_STATE_INCOMING: return Call.State.INCOMING; + case PRECISE_CALL_STATE_WAITING: return Call.State.WAITING; + case PRECISE_CALL_STATE_INCOMING_SETUP: return Call.State.INCOMING; + default: + } + return Call.State.DISCONNECTED; + } + + /** Returns the type of the call */ + public int getType() { + return mType; + } + + /** Returns the state */ + public Call.State getState() { + return mState; + } + + /** Updates the state */ + public void setState(Call.State state) { + mState = state; + } + + /** Returns the sub state */ + public int getSubState() { + return mSubstate; + } + + /** Returns the ringback tone type */ + public int getRingbackToneType() { + return mRingbackToneType; + } + + /** true if it is a multi-party call */ + public boolean isMultiParty() { + return mIsMpty; + } + + /** true if it is a mobile terminated call */ + public boolean isIncoming() { + return mIsMT; + } + + /** Returns the remote party nummber */ + public String getNumber() { + return mNumber; + } + + /** Returns the number presentation */ + public int getNumberPresentation() { + return mNumPresentation; + } + + /** Returns the remote party name */ + public String getName() { + return mName; + } + + /** Returns the name presentation */ + public int getNamePresentation() { + return mNamePresentation; + } + + /** + * Build a human representation of a connection instance, suitable for debugging. + * Don't log personal stuff unless in debug mode. + * @return a string representing the internal state of this connection. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(" type:").append(getType()); + sb.append(", state:").append(getState()); + sb.append(", subState:").append(getSubState()); + sb.append(", toneType:").append(getRingbackToneType()); + sb.append(", mpty:").append(isMultiParty()); + sb.append(", incoming:").append(isIncoming()); + sb.append(", numberPresentation:").append(getNumberPresentation()); + sb.append(", number:").append(Rlog.pii(TAG, getNumber())); + sb.append(", namePresentation:").append(getNamePresentation()); + sb.append(", name:").append(Rlog.pii(TAG, getName())); + return sb.toString(); + } +} diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java deleted file mode 100644 index b05e600cc3bb8537e549cf5cacb118412ae2bce5..0000000000000000000000000000000000000000 --- a/src/java/com/android/internal/telephony/SubscriptionController.java +++ /dev/null @@ -1,5020 +0,0 @@ -/* -* Copyright (C) 2014 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; - -import static android.Manifest.permission.READ_PHONE_NUMBERS; -import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.telephony.TelephonyManager.MULTISIM_ALLOWED; -import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION; -import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.app.AppOpsManager; -import android.app.PendingIntent; -import android.app.compat.CompatChanges; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.ContentObserver; -import android.database.Cursor; -import android.net.Uri; -import android.os.Binder; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.ParcelUuid; -import android.os.PersistableBundle; -import android.os.RegistrantList; -import android.os.RemoteException; -import android.os.TelephonyServiceManager.ServiceRegisterer; -import android.os.UserHandle; -import android.provider.Settings; -import android.provider.Telephony.SimInfo; -import android.telecom.PhoneAccountHandle; -import android.telecom.TelecomManager; -import android.telephony.AnomalyReporter; -import android.telephony.CarrierConfigManager; -import android.telephony.RadioAccessFamily; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.SubscriptionManager.SimDisplayNameSource; -import android.telephony.SubscriptionManager.UsageSetting; -import android.telephony.TelephonyFrameworkInitializer; -import android.telephony.TelephonyManager; -import android.telephony.TelephonyRegistryManager; -import android.telephony.UiccAccessRule; -import android.telephony.UiccPortInfo; -import android.telephony.UiccSlotInfo; -import android.telephony.UiccSlotMapping; -import android.telephony.euicc.EuiccManager; -import android.text.TextUtils; -import android.util.EventLog; -import android.util.LocalLog; -import android.util.Log; - -import com.android.ims.ImsManager; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.data.PhoneSwitcher; -import com.android.internal.telephony.metrics.TelephonyMetrics; -import com.android.internal.telephony.subscription.SubscriptionManagerService; -import com.android.internal.telephony.uicc.IccUtils; -import com.android.internal.telephony.uicc.UiccCard; -import com.android.internal.telephony.uicc.UiccController; -import com.android.internal.telephony.uicc.UiccProfile; -import com.android.internal.telephony.uicc.UiccSlot; -import com.android.internal.telephony.util.ArrayUtils; -import com.android.internal.telephony.util.TelephonyUtils; -import com.android.telephony.Rlog; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -/** - * Implementation of the ISub interface. - * - * Any setters which take subId, slotIndex or phoneId as a parameter will throw an exception if the - * parameter equals the corresponding INVALID_XXX_ID or DEFAULT_XXX_ID. - * - * All getters will lookup the corresponding default if the parameter is DEFAULT_XXX_ID. Ie calling - * getPhoneId(DEFAULT_SUB_ID) will return the same as getPhoneId(getDefaultSubId()). - * - * Finally, any getters which perform the mapping between subscriptions, slots and phones will - * return the corresponding INVALID_XXX_ID if the parameter is INVALID_XXX_ID. All other getters - * will fail and return the appropriate error value. Ie calling - * getSlotIndex(INVALID_SUBSCRIPTION_ID) will return INVALID_SIM_SLOT_INDEX and calling - * getSubInfoForSubscriber(INVALID_SUBSCRIPTION_ID) will return null. - * - */ -public class SubscriptionController extends ISub.Stub { - private static final String LOG_TAG = "SubscriptionController"; - private static final boolean DBG = false; - private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE); - private static final boolean DBG_CACHE = false; - private static final int DEPRECATED_SETTING = -1; - private static final ParcelUuid INVALID_GROUP_UUID = - ParcelUuid.fromString(CarrierConfigManager.REMOVE_GROUP_UUID_STRING); - private final LocalLog mLocalLog = new LocalLog(128); - private static final int SUB_ID_FOUND = 1; - private static final int NO_ENTRY_FOR_SLOT_INDEX = -1; - private static final int SUB_ID_NOT_IN_SLOT = -2; - - // Lock that both mCacheActiveSubInfoList and mCacheOpportunisticSubInfoList use. - private Object mSubInfoListLock = new Object(); - - /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */ - private final List mCacheActiveSubInfoList = new ArrayList<>(); - - /* Similar to mCacheActiveSubInfoList but only caching opportunistic subscriptions. */ - private List mCacheOpportunisticSubInfoList = new ArrayList<>(); - private AtomicBoolean mOpptSubInfoListChangedDirtyBit = new AtomicBoolean(); - - private static final Comparator SUBSCRIPTION_INFO_COMPARATOR = - (arg0, arg1) -> { - // Primary sort key on SimSlotIndex - int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex(); - if (flag == 0) { - // Secondary sort on SubscriptionId - return arg0.getSubscriptionId() - arg1.getSubscriptionId(); - } - return flag; - }; - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected final Object mLock = new Object(); - - /** The singleton instance. */ - protected static SubscriptionController sInstance = null; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected Context mContext; - protected TelephonyManager mTelephonyManager; - protected UiccController mUiccController; - - /** - * Apps targeting on Android T and beyond will get an empty list if there is no access to device - * identifiers nor has carrier privileges when calling - * SubscriptionManager#getSubscriptionsInGroup. - */ - @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) - public static final long REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID = 213902861L; - - private AppOpsManager mAppOps; - - // Each slot can have multiple subs. - private static class WatchedSlotIndexToSubIds { - private final Map> mSlotIndexToSubIds = - new ConcurrentHashMap<>(); - - public void clear() { - mSlotIndexToSubIds.clear(); - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } - - public Set>> entrySet() { - return mSlotIndexToSubIds.entrySet(); - } - - // Force all updates to data structure through wrapper. - public ArrayList getCopy(int slotIndex) { - ArrayList subIdList = mSlotIndexToSubIds.get(slotIndex); - if (subIdList == null) { - return null; - } - - return new ArrayList<>(subIdList); - } - - public void put(int slotIndex, ArrayList value) { - mSlotIndexToSubIds.put(slotIndex, value); - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } - - public void remove(int slotIndex) { - mSlotIndexToSubIds.remove(slotIndex); - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } - - public int size() { - return mSlotIndexToSubIds.size(); - } - - @VisibleForTesting - public Map> getMap() { - return mSlotIndexToSubIds; - } - - public int removeFromSubIdList(int slotIndex, int subId) { - ArrayList subIdList = mSlotIndexToSubIds.get(slotIndex); - if (subIdList == null) { - return NO_ENTRY_FOR_SLOT_INDEX; - } else { - if (subIdList.contains(subId)) { - subIdList.remove(new Integer(subId)); - if (subIdList.isEmpty()) { - mSlotIndexToSubIds.remove(slotIndex); - } - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - return SUB_ID_FOUND; - } else { - return SUB_ID_NOT_IN_SLOT; - } - } - } - - public void addToSubIdList(int slotIndex, Integer value) { - ArrayList subIdList = mSlotIndexToSubIds.get(slotIndex); - if (subIdList == null) { - subIdList = new ArrayList(); - subIdList.add(value); - mSlotIndexToSubIds.put(slotIndex, subIdList); - } else { - subIdList.add(value); - } - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } - - public void clearSubIdList(int slotIndex) { - ArrayList subIdList = mSlotIndexToSubIds.get(slotIndex); - if (subIdList != null) { - subIdList.clear(); - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } - } - } - - public static class WatchedInt { - private int mValue; - - public WatchedInt(int initialValue) { - mValue = initialValue; - } - - public int get() { - return mValue; - } - - public void set(int newValue) { - mValue = newValue; - } - } - - private final WatchedSlotIndexToSubIds mSlotIndexToSubIds = new WatchedSlotIndexToSubIds(); - - private final WatchedInt mDefaultFallbackSubId = - new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - @Override - public void set(int newValue) { - super.set(newValue); - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } - }; - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX; - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private int[] colorArr; - private long mLastISubServiceRegTime; - private RegistrantList mUiccAppsEnableChangeRegList = new RegistrantList(); - - // The properties that should be shared and synced across grouped subscriptions. - private static final Set GROUP_SHARING_PROPERTIES = new HashSet<>(Arrays.asList( - SubscriptionManager.ENHANCED_4G_MODE_ENABLED, - SubscriptionManager.VT_IMS_ENABLED, - SubscriptionManager.WFC_IMS_ENABLED, - SubscriptionManager.WFC_IMS_MODE, - SubscriptionManager.WFC_IMS_ROAMING_MODE, - SubscriptionManager.WFC_IMS_ROAMING_ENABLED, - SubscriptionManager.DATA_ROAMING, - SubscriptionManager.DISPLAY_NAME, - SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES, - SubscriptionManager.UICC_APPLICATIONS_ENABLED, - SubscriptionManager.IMS_RCS_UCE_ENABLED, - SubscriptionManager.CROSS_SIM_CALLING_ENABLED, - SubscriptionManager.NR_ADVANCED_CALLING_ENABLED, - SubscriptionManager.USER_HANDLE - )); - - public static SubscriptionController init(Context c) { - synchronized (SubscriptionController.class) { - if (sInstance == null) { - sInstance = new SubscriptionController(c); - } else { - Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); - } - return sInstance; - } - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static SubscriptionController getInstance() { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - throw new RuntimeException("getInstance should not be called."); - } - if (sInstance == null) { - Log.wtf(LOG_TAG, "getInstance null"); - } - - return sInstance; - } - - protected SubscriptionController(Context c) { - internalInit(c); - migrateImsSettings(); - } - - protected void internalInit(Context c) { - mContext = c; - mTelephonyManager = TelephonyManager.from(mContext); - - try { - mUiccController = UiccController.getInstance(); - } catch(RuntimeException ex) { - throw new RuntimeException( - "UiccController has to be initialised before SubscriptionController init"); - } - - mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); - - ServiceRegisterer subscriptionServiceRegisterer = TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getSubscriptionServiceRegisterer(); - if (subscriptionServiceRegisterer.get() == null) { - subscriptionServiceRegisterer.register(this); - mLastISubServiceRegTime = System.currentTimeMillis(); - } - - // clear SLOT_INDEX for all subs - clearSlotIndexForSubInfoRecords(); - - // Cache Setting values - cacheSettingValues(); - - // Initial invalidate activates caching. - invalidateDefaultSubIdCaches(); - invalidateDefaultDataSubIdCaches(); - invalidateDefaultSmsSubIdCaches(); - invalidateActiveDataSubIdCaches(); - invalidateSlotIndexCaches(); - - mContext.getContentResolver().registerContentObserver( - SubscriptionManager.SIM_INFO_SUW_RESTORE_CONTENT_URI, false, - new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange, Uri uri) { - if (uri.equals(SubscriptionManager.SIM_INFO_SUW_RESTORE_CONTENT_URI)) { - refreshCachedActiveSubscriptionInfoList(); - notifySubscriptionInfoChanged(); - - SubscriptionManager subManager = SubscriptionManager.from(mContext); - for (SubscriptionInfo subInfo : getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag())) { - if (SubscriptionController.getInstance() - .isActiveSubId(subInfo.getSubscriptionId())) { - ImsManager imsManager = ImsManager.getInstance(mContext, - subInfo.getSimSlotIndex()); - imsManager.updateImsServiceConfig(); - } - } - } - } - }); - - SubscriptionManager.invalidateSubscriptionManagerServiceEnabledCaches(); - - if (DBG) logdl("[SubscriptionController] init by Context"); - } - - /** - * Should only be triggered once. - */ - public void notifySubInfoReady() { - // broadcast default subId. - sendDefaultChangedBroadcast(SubscriptionManager.getDefaultSubscriptionId()); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private boolean isSubInfoReady() { - return SubscriptionInfoUpdater.isSubInfoInitialized(); - } - - /** - * This function marks SIM_SLOT_INDEX as INVALID for all subscriptions in the database. This - * should be done as part of initialization. - * - * TODO: SIM_SLOT_INDEX is based on current state and should not even be persisted in the - * database. - */ - private void clearSlotIndexForSubInfoRecords() { - if (mContext == null) { - logel("[clearSlotIndexForSubInfoRecords] TelephonyManager or mContext is null"); - return; - } - - // Update all subscriptions in simInfo db with invalid slot index - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX); - mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, null, null); - } - - /** - * Cache the Settings values by reading these values from Setting from disk to prevent disk I/O - * access during the API calling. This is based on an assumption that the Settings system will - * itself cache this value after the first read and thus only the first read after boot will - * access the disk. - */ - private void cacheSettingValues() { - Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - - Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - - Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected void enforceModifyPhoneState(String message) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE, message); - } - - private void enforceReadPrivilegedPhoneState(String message) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message); - } - - private void enforceManageSubscriptionUserAssociation(String message) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION, message); - } - - /** - * Returns whether the {@code callingPackage} has access to subscriber identifiers on the - * specified {@code subId} using the provided {@code message} in any resulting - * SecurityException. - */ - private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage, - String callingFeatureId, String message, boolean reportFailure) { - try { - return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId, - callingPackage, callingFeatureId, message, reportFailure); - } catch (SecurityException e) { - // A SecurityException indicates that the calling package is targeting at least the - // minimum level that enforces identifier access restrictions and the new access - // requirements are not met. - return false; - } - } - - /** - * Returns whether the {@code callingPackage} has access to the phone number on the specified - * {@code subId} using the provided {@code message} in any resulting SecurityException. - */ - private boolean hasPhoneNumberAccess(int subId, String callingPackage, String callingFeatureId, - String message) { - try { - return TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(mContext, subId, - callingPackage, callingFeatureId, message); - } catch (SecurityException e) { - return false; - } - } - - /** - * Notify the changed of subscription info. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void notifySubscriptionInfoChanged() { - TelephonyRegistryManager trm = - (TelephonyRegistryManager) - mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); - if (DBG) logd("notifySubscriptionInfoChanged:"); - trm.notifySubscriptionInfoChanged(); - - MultiSimSettingController.getInstance().notifySubscriptionInfoChanged(); - TelephonyMetrics metrics = TelephonyMetrics.getInstance(); - List subInfos; - synchronized (mSubInfoListLock) { - subInfos = new ArrayList<>(mCacheActiveSubInfoList); - } - - if (mOpptSubInfoListChangedDirtyBit.getAndSet(false)) { - notifyOpportunisticSubscriptionInfoChanged(); - } - metrics.updateActiveSubscriptionInfoList(subInfos); - } - - /** - * New SubInfoRecord instance and fill in detail info - * @param cursor The database cursor - * @return the query result of desired SubInfoRecord - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private SubscriptionInfo getSubInfoRecord(Cursor cursor) { - SubscriptionInfo.Builder builder = new SubscriptionInfo.Builder(); - int id = cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID)); - builder.setId(id) - .setIccId(cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.ICC_ID))) - .setSimSlotIndex(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.SIM_SLOT_INDEX))) - .setDisplayName(cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.DISPLAY_NAME))) - .setCarrierName(cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.CARRIER_NAME))) - .setDisplayNameSource(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.NAME_SOURCE))) - .setIconTint(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.HUE))) - .setDataRoaming(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.DATA_ROAMING))) - .setMcc(cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.MCC_STRING))) - .setMnc(cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.MNC_STRING))); - - String ehplmnsRaw = cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.EHPLMNS)); - String hplmnsRaw = cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.HPLMNS)); - String[] ehplmns = ehplmnsRaw == null ? null : ehplmnsRaw.split(","); - String[] hplmns = hplmnsRaw == null ? null : hplmnsRaw.split(","); - - builder.setEhplmns(ehplmns).setHplmns(hplmns); - - - // CARD_ID is the private ICCID/EID string, also known as the card string - String cardString = cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.CARD_ID)); - builder.setCardString(cardString); - // publicCardId is the publicly exposed int card ID - int publicCardId = mUiccController.convertToPublicCardId(cardString); - builder.setCardId(publicCardId); - - builder.setCountryIso(cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.ISO_COUNTRY_CODE))) - .setCarrierId(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.CARRIER_ID))); - - boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.IS_EMBEDDED)) == 1; - builder.setEmbedded(isEmbedded); - if (isEmbedded) { - builder.setNativeAccessRules(UiccAccessRule.decodeRules(cursor.getBlob( - cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES)))); - } - - builder.setCarrierConfigAccessRules(UiccAccessRule.decodeRules(cursor.getBlob( - cursor.getColumnIndexOrThrow( - SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS)))) - .setOpportunistic(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.IS_OPPORTUNISTIC)) == 1) - .setGroupUuid(cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.GROUP_UUID))) - .setProfileClass(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.PROFILE_CLASS))) - .setPortIndex(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.PORT_INDEX))) - .setType(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.SUBSCRIPTION_TYPE))) - .setGroupOwner(getOptionalStringFromCursor(cursor, SubscriptionManager.GROUP_OWNER, - /*defaultVal*/ null)) - .setUiccApplicationsEnabled(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.UICC_APPLICATIONS_ENABLED)) == 1) - .setUsageSetting(cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.USAGE_SETTING))); - - // If line1number has been set to a different number, use it instead. - String number = cursor.getString(cursor.getColumnIndexOrThrow( - SubscriptionManager.NUMBER)); - String line1Number = mTelephonyManager.getLine1Number(id); - if (!TextUtils.isEmpty(line1Number) && !line1Number.equals(number)) { - number = line1Number; - } - builder.setNumber(number); - - // FIXME(b/210771052): constructing a complete SubscriptionInfo requires a port index, - // but the port index isn't available here. Should it actually be part of SubscriptionInfo? - - return builder.build(); - } - - private String getOptionalStringFromCursor(Cursor cursor, String column, String defaultVal) { - // Return defaultVal if the column doesn't exist. - int columnIndex = cursor.getColumnIndex(column); - return (columnIndex == -1) ? defaultVal : cursor.getString(columnIndex); - } - - /** - * Get a subscription that matches IccId. - * @return null if there isn't a match, or subscription info if there is one. - */ - public SubscriptionInfo getSubInfoForIccId(String iccId) { - List info = getSubInfo( - SubscriptionManager.ICC_ID + "=\'" + iccId + "\'", null); - if (info == null || info.size() == 0) return null; - // Should be at most one subscription with the iccid. - return info.get(0); - } - - /** - * Query SubInfoRecord(s) from subinfo database - * @param selection A filter declaring which rows to return - * @param queryKey query key content - * @return Array list of queried result from database - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public List getSubInfo(String selection, Object queryKey) { - if (VDBG) logd("selection:" + selection + ", querykey: " + queryKey); - String[] selectionArgs = null; - if (queryKey != null) { - selectionArgs = new String[] {queryKey.toString()}; - } - ArrayList subList = null; - Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI, - null, selection, selectionArgs, null); - try { - if (cursor != null) { - while (cursor.moveToNext()) { - SubscriptionInfo subInfo = getSubInfoRecord(cursor); - if (subInfo != null) { - if (subList == null) { - subList = new ArrayList(); - } - subList.add(subInfo); - } - } - } else { - if (DBG) logd("Query fail"); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return subList; - } - - /** - * Find unused color to be set for new SubInfoRecord - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @return RGB integer value of color - */ - private int getUnusedColor(String callingPackage, String callingFeatureId) { - List availableSubInfos = getActiveSubscriptionInfoList(callingPackage, - callingFeatureId); - colorArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors); - int colorIdx = 0; - - if (availableSubInfos != null) { - for (int i = 0; i < colorArr.length; i++) { - int j; - for (j = 0; j < availableSubInfos.size(); j++) { - if (colorArr[i] == availableSubInfos.get(j).getIconTint()) { - break; - } - } - if (j == availableSubInfos.size()) { - return colorArr[i]; - } - } - colorIdx = availableSubInfos.size() % colorArr.length; - } - return colorArr[colorIdx]; - } - - @Deprecated - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) { - return getActiveSubscriptionInfo(subId, callingPackage, null); - } - - /** - * Get the active SubscriptionInfo with the subId key - * @param subId The unique SubscriptionInfo key in database - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @return SubscriptionInfo, maybe null if its not active - */ - @Override - public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage, - String callingFeatureId) { - if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage, - callingFeatureId, "getActiveSubscriptionInfo")) { - return null; - } - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - List subList; - try { - subList = getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - } finally { - Binder.restoreCallingIdentity(identity); - } - if (subList != null) { - for (SubscriptionInfo si : subList) { - if (si.getSubscriptionId() == subId) { - if (VDBG) { - logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si); - } - return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId, - "getActiveSubscriptionInfo"); - } - } - } - if (DBG) { - logd("[getActiveSubscriptionInfo]- subId=" + subId - + " subList=" + subList + " subInfo=null"); - } - - return null; - } - - /** - * Get a single subscription info record for a given subscription. - * - * @param subId the subId to query. - * - * @hide - */ - public SubscriptionInfo getSubscriptionInfo(int subId) { - synchronized (mSubInfoListLock) { - // check cache for active subscriptions first, before querying db - for (SubscriptionInfo subInfo : mCacheActiveSubInfoList) { - if (subInfo.getSubscriptionId() == subId) { - return subInfo; - } - } - - // check cache for opportunistic subscriptions too, before querying db - for (SubscriptionInfo subInfo : mCacheOpportunisticSubInfoList) { - if (subInfo.getSubscriptionId() == subId) { - return subInfo; - } - } - } - - List subInfoList = getSubInfo( - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null); - if (subInfoList == null || subInfoList.isEmpty()) return null; - return subInfoList.get(0); - } - - /** - * Get the active SubscriptionInfo associated with the iccId - * @param iccId the IccId of SIM card - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @return SubscriptionInfo, maybe null if its not active - */ - @Override - public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage, - String callingFeatureId) { - enforceReadPrivilegedPhoneState("getActiveSubscriptionInfoForIccId"); - return getActiveSubscriptionInfoForIccIdInternal(iccId); - } - - /** - * Get the active SubscriptionInfo associated with the given iccId. The caller *must* perform - * permission checks when using this method. - */ - private SubscriptionInfo getActiveSubscriptionInfoForIccIdInternal(String iccId) { - if (iccId == null) { - return null; - } - - final long identity = Binder.clearCallingIdentity(); - try { - List subList = getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - if (subList != null) { - for (SubscriptionInfo si : subList) { - if (iccId.equals(si.getIccId())) { - if (DBG) - logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId + " subInfo=" + si); - return si; - } - } - } - if (DBG) { - logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId - + " subList=" + subList + " subInfo=null"); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - - return null; - } - - /** - * Get the active SubscriptionInfo associated with the slotIndex. - * This API does not return details on Remote-SIM subscriptions. - * @param slotIndex the slot which the subscription is inserted - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex. - */ - @Override - public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, - String callingPackage, String callingFeatureId) { - Phone phone = PhoneFactory.getPhone(slotIndex); - if (phone == null) { - if (DBG) { - loge("[getActiveSubscriptionInfoForSimSlotIndex] no phone, slotIndex=" + slotIndex); - } - return null; - } - if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( - mContext, phone.getSubId(), callingPackage, callingFeatureId, - "getActiveSubscriptionInfoForSimSlotIndex")) { - return null; - } - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - List subList; - try { - subList = getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - } finally { - Binder.restoreCallingIdentity(identity); - } - if (subList != null) { - for (SubscriptionInfo si : subList) { - if (si.getSimSlotIndex() == slotIndex) { - if (DBG) { - logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" - + slotIndex + " subId=" + si); - } else { - logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" - + slotIndex + " subId=" + conditionallyRemoveIdentifiers(si, false, - false)); - } - return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId, - "getActiveSubscriptionInfoForSimSlotIndex"); - } - } - if (DBG) { - logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex - + " subId=null"); - } - } else { - if (DBG) { - logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null"); - } - } - - - return null; - } - - /** - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @return List of all SubscriptionInfo records in database, - * include those that were inserted before, maybe empty but not null. - * @hide - */ - @Override - public List getAllSubInfoList(String callingPackage, - String callingFeatureId) { - return getAllSubInfoList(callingPackage, callingFeatureId, false); - } - - /** - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @param skipConditionallyRemoveIdentifier if set, skip removing identifier conditionally - * @return List of all SubscriptionInfo records in database, - * include those that were inserted before, maybe empty but not null. - * @hide - */ - public List getAllSubInfoList(String callingPackage, - String callingFeatureId, boolean skipConditionallyRemoveIdentifier) { - if (VDBG) logd("[getAllSubInfoList]+"); - - // This API isn't public, so no need to provide a valid subscription ID - we're not worried - // about carrier-privileged callers not having access. - if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( - mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, - callingFeatureId, "getAllSubInfoList")) { - return null; - } - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - List subList; - try { - subList = getSubInfo(null, null); - } finally { - Binder.restoreCallingIdentity(identity); - } - if (subList != null && !skipConditionallyRemoveIdentifier) { - if (VDBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return"); - subList = subList.stream().map( - subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo, - callingPackage, callingFeatureId, "getAllSubInfoList")) - .collect(Collectors.toList()); - } else { - if (VDBG) logd("[getAllSubInfoList]- no info return"); - } - return subList; - } - - private List makeCacheListCopyWithLock(List cacheSubList) { - synchronized (mSubInfoListLock) { - return new ArrayList<>(cacheSubList); - } - } - - @Deprecated - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public List getActiveSubscriptionInfoList(String callingPackage) { - return getSubscriptionInfoListFromCacheHelper(callingPackage, null, - makeCacheListCopyWithLock(mCacheActiveSubInfoList)); - } - - /** - * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local - * and remote SIMs. - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @return Array list of currently inserted SubInfoRecord(s) - */ - @Override - public List getActiveSubscriptionInfoList(String callingPackage, - String callingFeatureId) { - return getSubscriptionInfoListFromCacheHelper(callingPackage, callingFeatureId, - makeCacheListCopyWithLock(mCacheActiveSubInfoList)); - } - - /** - * Refresh the cache of SubInfoRecord(s) of the currently available SIM(s) - including - * local & remote SIMs. - */ - @VisibleForTesting // For mockito to mock this method - public void refreshCachedActiveSubscriptionInfoList() { - boolean opptSubListChanged; - - List activeSubscriptionInfoList = getSubInfo( - SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR " - + SubscriptionManager.SUBSCRIPTION_TYPE + "=" - + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM, - null); - - synchronized (mSubInfoListLock) { - if (activeSubscriptionInfoList != null) { - // Log when active sub info changes. - if (mCacheActiveSubInfoList.size() != activeSubscriptionInfoList.size() - || !mCacheActiveSubInfoList.containsAll(activeSubscriptionInfoList)) { - logdl("Active subscription info list changed. " + activeSubscriptionInfoList); - } - - mCacheActiveSubInfoList.clear(); - activeSubscriptionInfoList.sort(SUBSCRIPTION_INFO_COMPARATOR); - mCacheActiveSubInfoList.addAll(activeSubscriptionInfoList); - } else { - logd("activeSubscriptionInfoList is null."); - mCacheActiveSubInfoList.clear(); - } - if (DBG_CACHE) { - if (!mCacheActiveSubInfoList.isEmpty()) { - for (SubscriptionInfo si : mCacheActiveSubInfoList) { - logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached info=" - + si); - } - } else { - logdl("[refreshCachedActiveSubscriptionInfoList]- no info return"); - } - } - } - - // Refresh cached opportunistic sub list and detect whether it's changed. - refreshCachedOpportunisticSubscriptionInfoList(); - } - - @Deprecated - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public int getActiveSubInfoCount(String callingPackage) { - return getActiveSubInfoCount(callingPackage, null); - } - - /** - * Get the SUB count of active SUB(s) - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package. - * @return active SIM count - */ - @Override - public int getActiveSubInfoCount(String callingPackage, String callingFeatureId) { - // Let getActiveSubscriptionInfoList perform permission checks / filtering. - List records = getActiveSubscriptionInfoList(callingPackage, - callingFeatureId); - if (records == null) { - if (VDBG) logd("[getActiveSubInfoCount] records null"); - return 0; - } - if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size()); - return records.size(); - } - - /** - * Get the SUB count of all SUB(s) in SubscriptoinInfo database - * @param callingPackage The package making the IPC. - * @param callingFeatureId The feature in the package - * @return all SIM count in database, include what was inserted before - */ - public int getAllSubInfoCount(String callingPackage, String callingFeatureId) { - if (DBG) logd("[getAllSubInfoCount]+"); - - // This API isn't public, so no need to provide a valid subscription ID - we're not worried - // about carrier-privileged callers not having access. - if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( - mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, - callingFeatureId, "getAllSubInfoCount")) { - return 0; - } - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI, - null, null, null, null); - try { - if (cursor != null) { - int count = cursor.getCount(); - if (DBG) logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB"); - return count; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - if (DBG) logd("[getAllSubInfoCount]- no SUB in DB"); - - return 0; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * @return the maximum number of local subscriptions this device will support at any one time. - */ - @Override - public int getActiveSubInfoCountMax() { - // FIXME: This valid now but change to use TelephonyDevController in the future - return mTelephonyManager.getSimCount(); - } - - @Override - public List getAvailableSubscriptionInfoList(String callingPackage, - String callingFeatureId) { - try { - enforceReadPrivilegedPhoneState("getAvailableSubscriptionInfoList"); - } catch (SecurityException e) { - try { - mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE, null); - // If caller doesn't have READ_PRIVILEGED_PHONE_STATE permission but only - // has READ_PHONE_STATE permission, log this event. - EventLog.writeEvent(0x534e4554, "185235454", Binder.getCallingUid()); - } catch (SecurityException ex) { - // Ignore - } - throw new SecurityException("Need READ_PRIVILEGED_PHONE_STATE to call " - + " getAvailableSubscriptionInfoList"); - } - - // Now that all security checks pass, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - String selection = SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR " - + SubscriptionManager.SUBSCRIPTION_TYPE + "=" - + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM; - - EuiccManager euiccManager = - (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE); - if (euiccManager.isEnabled()) { - selection += " OR " + SubscriptionManager.IS_EMBEDDED + "=1"; - } - - // Available eSIM profiles are reported by EuiccManager. However for physical SIMs if - // they are in inactive slot or programmatically disabled, they are still considered - // available. In this case we get their iccid from slot info and include their - // subscriptionInfos. - List iccIds = getIccIdsOfInsertedPhysicalSims(); - - if (!iccIds.isEmpty()) { - selection += " OR (" + getSelectionForIccIdList(iccIds.toArray(new String[0])) - + ")"; - } - - List subList = getSubInfo(selection, null /* queryKey */); - - if (subList != null) { - subList.sort(SUBSCRIPTION_INFO_COMPARATOR); - - if (VDBG) logdl("[getAvailableSubInfoList]- " + subList.size() + " infos return"); - } else { - if (DBG) logdl("[getAvailableSubInfoList]- no info return"); - } - - return subList; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private List getIccIdsOfInsertedPhysicalSims() { - List ret = new ArrayList<>(); - UiccSlot[] uiccSlots = UiccController.getInstance().getUiccSlots(); - if (uiccSlots == null) return ret; - - for (UiccSlot uiccSlot : uiccSlots) { - if (uiccSlot != null && uiccSlot.getCardState() != null - && uiccSlot.getCardState().isCardPresent() - && !uiccSlot.isEuicc()) { - // Non euicc slots will have single port, so use default port index. - String iccId = uiccSlot.getIccId(TelephonyManager.DEFAULT_PORT_INDEX); - if (!TextUtils.isEmpty(iccId)) { - ret.add(IccUtils.stripTrailingFs(iccId)); - } - } - } - - return ret; - } - - @Override - public List getAccessibleSubscriptionInfoList(String callingPackage) { - EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE); - if (!euiccManager.isEnabled()) { - if (DBG) { - logdl("[getAccessibleSubInfoList] Embedded subscriptions are disabled"); - } - return null; - } - - // Verify that the given package belongs to the calling UID. - mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - - // Perform the operation as ourselves. If the caller cannot read phone state, they may still - // have carrier privileges per the subscription metadata, so we always need to make the - // query and then filter the results. - final long identity = Binder.clearCallingIdentity(); - List subList; - try { - subList = getSubInfo(SubscriptionManager.IS_EMBEDDED + "=1", null); - } finally { - Binder.restoreCallingIdentity(identity); - } - - if (subList == null) { - if (DBG) logdl("[getAccessibleSubInfoList] No info returned"); - return null; - } - - // Filter the list to only include subscriptions which the (restored) caller can manage. - List filteredList = subList.stream() - .filter(subscriptionInfo -> - subscriptionInfo.canManageSubscription(mContext, callingPackage)) - .sorted(SUBSCRIPTION_INFO_COMPARATOR) - .collect(Collectors.toList()); - if (VDBG) { - logdl("[getAccessibleSubInfoList] " + filteredList.size() + " infos returned"); - } - return filteredList; - } - - /** - * Return the list of subscriptions in the database which are either: - *
    - *
  • Embedded (but see note about {@code includeNonRemovableSubscriptions}, or - *
  • In the given list of current embedded ICCIDs (which may not yet be in the database, or - * which may not currently be marked as embedded). - *
- * - *

NOTE: This is not accessible to external processes, so it does not need a permission - * check. It is only intended for use by {@link SubscriptionInfoUpdater}. - * - * @param embeddedIccids all ICCIDs of available embedded subscriptions. This is used to surface - * entries for profiles which had been previously deleted. - * @param isEuiccRemovable whether the current ICCID is removable. Non-removable subscriptions - * will only be returned if the current ICCID is not removable; otherwise, they are left - * alone (not returned here unless in the embeddedIccids list) under the assumption that - * they will still be accessible when the eUICC containing them is activated. - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public List getSubscriptionInfoListForEmbeddedSubscriptionUpdate( - String[] embeddedIccids, boolean isEuiccRemovable) { - StringBuilder whereClause = new StringBuilder(); - whereClause.append("(").append(SubscriptionManager.IS_EMBEDDED).append("=1"); - if (isEuiccRemovable) { - // Current eUICC is removable, so don't return non-removable subscriptions (which would - // be deleted), as these are expected to still be present on a different, non-removable - // eUICC. - whereClause.append(" AND ").append(SubscriptionManager.IS_REMOVABLE).append("=1"); - } - // Else, return both removable and non-removable subscriptions. This is expected to delete - // all removable subscriptions, which is desired as they may not be accessible. - - whereClause.append(") OR ").append(SubscriptionManager.ICC_ID).append(" IN ("); - // ICCIDs are validated to contain only numbers when passed in, and come from a trusted - // app, so no need to escape. - for (int i = 0; i < embeddedIccids.length; i++) { - if (i > 0) { - whereClause.append(","); - } - whereClause.append("'").append(embeddedIccids[i]).append("'"); - } - whereClause.append(")"); - - List list = getSubInfo(whereClause.toString(), null); - if (list == null) { - return Collections.emptyList(); - } - return list; - } - - @Override - public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS, - "requestEmbeddedSubscriptionInfoListRefresh"); - long token = Binder.clearCallingIdentity(); - try { - PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(cardId, null /* callback */); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Asynchronously refresh the embedded subscription info list for the embedded card has the - * given card id {@code cardId}. - * - * @param callback Optional callback to execute after the refresh completes. Must terminate - * quickly as it will be called from SubscriptionInfoUpdater's handler thread. - */ - // No permission check needed as this is not exposed via AIDL. - public void requestEmbeddedSubscriptionInfoListRefresh( - int cardId, @Nullable Runnable callback) { - PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(cardId, callback); - } - - /** - * Asynchronously refresh the embedded subscription info list for the embedded card has the - * default card id return by {@link TelephonyManager#getCardIdForDefaultEuicc()}. - * - * @param callback Optional callback to execute after the refresh completes. Must terminate - * quickly as it will be called from SubscriptionInfoUpdater's handler thread. - */ - // No permission check needed as this is not exposed via AIDL. - public void requestEmbeddedSubscriptionInfoListRefresh(@Nullable Runnable callback) { - PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh( - mTelephonyManager.getCardIdForDefaultEuicc(), callback); - } - - /** - * Add a new subscription info record, if needed. - * @param uniqueId This is the unique identifier for the subscription within the specific - * subscription type. - * @param displayName human-readable name of the device the subscription corresponds to. - * @param slotIndex value for {@link SubscriptionManager#SIM_SLOT_INDEX} - * @param subscriptionType the type of subscription to be added. - * @return 0 if success, < 0 on error. - */ - @Override - public int addSubInfo(String uniqueId, String displayName, int slotIndex, - int subscriptionType) { - if (DBG) { - String iccIdStr = uniqueId; - if (!isSubscriptionForRemoteSim(subscriptionType)) { - iccIdStr = SubscriptionInfo.givePrintableIccid(uniqueId); - } - logdl("[addSubInfoRecord]+ iccid: " + iccIdStr - + ", slotIndex: " + slotIndex - + ", subscriptionType: " + subscriptionType); - } - - enforceModifyPhoneState("addSubInfo"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - if (uniqueId == null) { - if (DBG) logdl("[addSubInfo]- null iccId"); - return -1; - } - - ContentResolver resolver = mContext.getContentResolver(); - String selection = SubscriptionManager.ICC_ID + "=?"; - String[] args; - if (isSubscriptionForRemoteSim(subscriptionType)) { - PackageManager packageManager = mContext.getPackageManager(); - if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { - logel("[addSubInfo] Remote SIM can only be added when FEATURE_AUTOMOTIVE" - + " is supported"); - return -1; - } - selection += " AND " + SubscriptionManager.SUBSCRIPTION_TYPE + "=?"; - args = new String[]{uniqueId, Integer.toString(subscriptionType)}; - } else { - selection += " OR " + SubscriptionManager.ICC_ID + "=?"; - args = new String[]{uniqueId, IccUtils.getDecimalSubstring(uniqueId)}; - } - Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI, - new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID, - SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE, - SubscriptionManager.ICC_ID, SubscriptionManager.CARD_ID, - SubscriptionManager.PORT_INDEX}, - selection, args, null); - - boolean setDisplayName = false; - try { - boolean recordsDoNotExist = (cursor == null || !cursor.moveToFirst()); - if (isSubscriptionForRemoteSim(subscriptionType)) { - if (recordsDoNotExist) { - // create a Subscription record - slotIndex = SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB; - Uri uri = insertEmptySubInfoRecord(uniqueId, displayName, - slotIndex, subscriptionType); - if (DBG) logd("[addSubInfoRecord] New record created: " + uri); - } else { - if (DBG) logdl("[addSubInfoRecord] Record already exists"); - } - } else { // Handle Local SIM devices - if (recordsDoNotExist) { - setDisplayName = true; - Uri uri = insertEmptySubInfoRecord(uniqueId, slotIndex); - if (DBG) logdl("[addSubInfoRecord] New record created: " + uri); - } else { // there are matching records in the database for the given ICC_ID - int subId = cursor.getInt(0); - int oldSimInfoId = cursor.getInt(1); - int nameSource = cursor.getInt(2); - String oldIccId = cursor.getString(3); - String oldCardId = cursor.getString(4); - int oldPortIndex = cursor.getInt(5); - ContentValues value = new ContentValues(); - - if (slotIndex != oldSimInfoId) { - value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex); - } - - if (oldIccId != null && oldIccId.length() < uniqueId.length() - && (oldIccId.equals(IccUtils.getDecimalSubstring(uniqueId)))) { - value.put(SubscriptionManager.ICC_ID, uniqueId); - } - - UiccCard card = mUiccController.getUiccCardForPhone(slotIndex); - if (card != null) { - String cardId = card.getCardId(); - if (cardId != null && cardId != oldCardId) { - value.put(SubscriptionManager.CARD_ID, cardId); - } - } - - //update portIndex for pSim - UiccSlot slot = mUiccController.getUiccSlotForPhone(slotIndex); - if (slot != null && !slot.isEuicc()) { - int portIndex = slot.getPortIndexFromIccId(uniqueId); - if (portIndex != oldPortIndex) { - value.put(SubscriptionManager.PORT_INDEX, portIndex); - } - } - - if (value.size() > 0) { - resolver.update(SubscriptionManager.getUriForSubscriptionId(subId), - value, null, null); - } - - if (DBG) logdl("[addSubInfoRecord] Record already exists"); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - selection = SubscriptionManager.SIM_SLOT_INDEX + "=?"; - args = new String[] {String.valueOf(slotIndex)}; - if (isSubscriptionForRemoteSim(subscriptionType)) { - selection = SubscriptionManager.ICC_ID + "=? AND " - + SubscriptionManager.SUBSCRIPTION_TYPE + "=?"; - args = new String[]{uniqueId, Integer.toString(subscriptionType)}; - } - cursor = resolver.query(SubscriptionManager.CONTENT_URI, null, - selection, args, null); - try { - if (cursor != null && cursor.moveToFirst()) { - do { - int subId = cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID)); - // If sSlotIndexToSubIds already has the same subId for a slotIndex/phoneId, - // do not add it. - if (addToSubIdList(slotIndex, subId, subscriptionType)) { - // TODO While two subs active, if user deactivats first - // one, need to update the default subId with second one. - - // FIXME: Currently we assume phoneId == slotIndex which in the future - // may not be true, for instance with multiple subs per slot. - // But is true at the moment. - int subIdCountMax = getActiveSubInfoCountMax(); - int defaultSubId = getDefaultSubId(); - if (DBG) { - logdl("[addSubInfoRecord]" - + " mSlotIndexToSubIds.size=" + mSlotIndexToSubIds.size() - + " slotIndex=" + slotIndex + " subId=" + subId - + " defaultSubId=" + defaultSubId - + " simCount=" + subIdCountMax); - } - - // Set the default sub if not set or if single sim device - if (!isSubscriptionForRemoteSim(subscriptionType)) { - if (!SubscriptionManager.isValidSubscriptionId(defaultSubId) - || subIdCountMax == 1 - || mDefaultFallbackSubId.get() == - SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - logdl("setting default fallback subid to " + subId); - setDefaultFallbackSubId(subId, subscriptionType); - } - // If single sim device, set this subscription as the default for - // everything - if (subIdCountMax == 1) { - if (DBG) { - logdl("[addSubInfoRecord] one sim set defaults to subId=" - + subId); - } - setDefaultDataSubId(subId); - setDefaultSmsSubId(subId); - setDefaultVoiceSubId(subId); - } - } else { - updateDefaultSubIdsIfNeeded(subId, subscriptionType); - } - } else { - if (DBG) { - logdl("[addSubInfoRecord] current SubId is already known, " - + "IGNORE"); - } - } - if (DBG) { - logdl("[addSubInfoRecord] hashmap(" + slotIndex + "," + subId + ")"); - } - } while (cursor.moveToNext()); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - // Refresh the Cache of Active Subscription Info List. This should be done after - // updating sSlotIndexToSubIds which is done through addToSubIdList() above. - refreshCachedActiveSubscriptionInfoList(); - - if (isSubscriptionForRemoteSim(subscriptionType)) { - notifySubscriptionInfoChanged(); - } else { // Handle Local SIM devices - // Set Display name after sub id is set above so as to get valid simCarrierName - int subId = getSubId(slotIndex); - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - if (DBG) { - logdl("[addSubInfoRecord]- getSubId failed invalid subId = " + subId); - } - return -1; - } - if (setDisplayName) { - String simCarrierName = mTelephonyManager.getSimOperatorName(subId); - String nameToSet; - - if (!TextUtils.isEmpty(simCarrierName)) { - nameToSet = simCarrierName; - } else { - nameToSet = "CARD " + Integer.toString(slotIndex + 1); - } - - ContentValues value = new ContentValues(); - value.put(SubscriptionManager.DISPLAY_NAME, nameToSet); - resolver.update(SubscriptionManager.getUriForSubscriptionId(subId), value, - null, null); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet); - } - - if (DBG) logdl("[addSubInfoRecord]- info size=" + mSlotIndexToSubIds.size()); - } - - } finally { - Binder.restoreCallingIdentity(identity); - } - return 0; - } - - private void updateDefaultSubIdsIfNeeded(int newDefault, int subscriptionType) { - if (DBG) { - logdl("[updateDefaultSubIdsIfNeeded] newDefault=" + newDefault - + ", subscriptionType=" + subscriptionType); - } - // Set the default ot new value only if the current default is invalid. - if (!isActiveSubscriptionId(getDefaultSubId())) { - // current default is not valid anylonger. set a new default - if (DBG) { - logdl("[updateDefaultSubIdsIfNeeded] set sDefaultFallbackSubId=" + newDefault); - } - setDefaultFallbackSubId(newDefault, subscriptionType); - } - - int value = getDefaultSmsSubId(); - if (!isActiveSubscriptionId(value)) { - // current default is not valid. set it to the given newDefault value - setDefaultSmsSubId(newDefault); - } - value = getDefaultDataSubId(); - if (!isActiveSubscriptionId(value)) { - setDefaultDataSubId(newDefault); - } - value = getDefaultVoiceSubId(); - if (!isActiveSubscriptionId(value)) { - setDefaultVoiceSubId(newDefault); - } - } - - /** - * This method returns true if the given subId is among the list of currently active - * subscriptions. - */ - private boolean isActiveSubscriptionId(int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId)) return false; - ArrayList subIdList = getActiveSubIdArrayList(); - if (subIdList.isEmpty()) return false; - return subIdList.contains(new Integer(subId)); - } - - /* - * Delete subscription info record for the given device. - * @param uniqueId This is the unique identifier for the subscription within the specific - * subscription type. - * @param subscriptionType the type of subscription to be removed - * @return 0 if success, < 0 on error. - */ - @Override - public int removeSubInfo(String uniqueId, int subscriptionType) { - enforceModifyPhoneState("removeSubInfo"); - if (DBG) { - logd("[removeSubInfo] uniqueId: " + uniqueId - + ", subscriptionType: " + subscriptionType); - } - - // validate the given info - does it exist in the active subscription list - int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - int slotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - synchronized (mSubInfoListLock) { - for (SubscriptionInfo info : mCacheActiveSubInfoList) { - if ((info.getSubscriptionType() == subscriptionType) - && info.getIccId().equalsIgnoreCase(uniqueId)) { - subId = info.getSubscriptionId(); - slotIndex = info.getSimSlotIndex(); - break; - } - } - } - if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - if (DBG) { - logd("Invalid subscription details: subscriptionType = " + subscriptionType - + ", uniqueId = " + uniqueId); - } - return -1; - } - - if (DBG) logd("removing the subid : " + subId); - - // Now that all security checks passes, perform the operation as ourselves. - int result = 0; - final long identity = Binder.clearCallingIdentity(); - try { - ContentResolver resolver = mContext.getContentResolver(); - result = resolver.delete(SubscriptionManager.CONTENT_URI, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=? AND " - + SubscriptionManager.SUBSCRIPTION_TYPE + "=?", - new String[]{Integer.toString(subId), Integer.toString(subscriptionType)}); - if (result != 1) { - if (DBG) { - logd("found NO subscription to remove with subscriptionType = " - + subscriptionType + ", uniqueId = " + uniqueId); - } - return -1; - } - refreshCachedActiveSubscriptionInfoList(); - result = mSlotIndexToSubIds.removeFromSubIdList(slotIndex, subId); - if (result == NO_ENTRY_FOR_SLOT_INDEX) { - loge("sSlotIndexToSubIds has no entry for slotIndex = " + slotIndex); - } else if (result == SUB_ID_NOT_IN_SLOT) { - loge("sSlotIndexToSubIds has no subid: " + subId + ", in index: " + slotIndex); - } - - // Since a subscription is removed, if this one is set as default for any setting, - // set some other subid as the default. - int newDefault = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - SubscriptionInfo info = null; - final List records = getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - if (!records.isEmpty()) { - // yes, we have more subscriptions. pick the first one. - // FIXME do we need a policy to figure out which one is to be next default - info = records.get(0); - } - updateDefaultSubIdsIfNeeded(info.getSubscriptionId(), info.getSubscriptionType()); - - notifySubscriptionInfoChanged(); - } finally { - Binder.restoreCallingIdentity(identity); - } - return result; - } - - /** - * Clear an subscriptionInfo to subinfo database if needed by updating slot index to invalid. - * @param slotIndex the slot which the SIM is removed - */ - public void clearSubInfoRecord(int slotIndex) { - if (DBG) logdl("[clearSubInfoRecord]+ iccId:" + " slotIndex:" + slotIndex); - - // update simInfo db with invalid slot index - ContentResolver resolver = mContext.getContentResolver(); - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX); - String where = "(" + SubscriptionManager.SIM_SLOT_INDEX + "=" + slotIndex + ")"; - resolver.update(SubscriptionManager.CONTENT_URI, value, where, null); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - boolean isFallBackRefreshRequired = false; - if (mDefaultFallbackSubId.get() > SubscriptionManager.INVALID_SUBSCRIPTION_ID && - mSlotIndexToSubIds.getCopy(slotIndex) != null && - mSlotIndexToSubIds.getCopy(slotIndex).contains(mDefaultFallbackSubId.get())) { - isFallBackRefreshRequired = true; - } - mSlotIndexToSubIds.remove(slotIndex); - // set mDefaultFallbackSubId to invalid in case mSlotIndexToSubIds do not have any entries - if (mSlotIndexToSubIds.size() ==0 ) { - mDefaultFallbackSubId.set(SubscriptionManager.INVALID_SUBSCRIPTION_ID); - } else if (isFallBackRefreshRequired) { - // set mDefaultFallbackSubId to valid subId from mSlotIndexToSubIds - for (int index = 0; index < getActiveSubIdArrayList().size(); index ++) { - int subId = getActiveSubIdArrayList().get(index); - if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - mDefaultFallbackSubId.set(subId); - break; - } - } - } - } - - /** - * Insert an empty SubInfo record into the database. - * - *

NOTE: This is not accessible to external processes, so it does not need a permission - * check. It is only intended for use by {@link SubscriptionInfoUpdater}. If there is a - * subscription record exist with the same ICCID, no new empty record will be created. - * - * @return the URL of the newly created row. Return null if no new empty record is - * created. - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - @Nullable - public Uri insertEmptySubInfoRecord(String iccId, int slotIndex) { - if (getSubInfoForIccId(iccId) != null) { - loge("insertEmptySubInfoRecord: Found existing record by ICCID. Do not create a " - + "new empty entry."); - return null; - } - return insertEmptySubInfoRecord(iccId, null, slotIndex, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - } - - Uri insertEmptySubInfoRecord(String uniqueId, String displayName, int slotIndex, - int subscriptionType) { - ContentResolver resolver = mContext.getContentResolver(); - ContentValues value = new ContentValues(); - value.put(SubscriptionManager.ICC_ID, uniqueId); - int color = getUnusedColor(mContext.getOpPackageName(), mContext.getAttributionTag()); - // default SIM color differs between slots - value.put(SubscriptionManager.HUE, color); - value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex); - value.put(SubscriptionManager.CARRIER_NAME, ""); - value.put(SubscriptionManager.CARD_ID, uniqueId); - value.put(SubscriptionManager.SUBSCRIPTION_TYPE, subscriptionType); - if (!TextUtils.isEmpty(displayName)) { - value.put(SubscriptionManager.DISPLAY_NAME, displayName); - } - if (!isSubscriptionForRemoteSim(subscriptionType)) { - UiccCard card = mUiccController.getUiccCardForPhone(slotIndex); - if (card != null) { - String cardId = card.getCardId(); - if (cardId != null) { - value.put(SubscriptionManager.CARD_ID, cardId); - } - } - UiccSlot slot = mUiccController.getUiccSlotForPhone(slotIndex); - if (slot != null) { - value.put(SubscriptionManager.PORT_INDEX, slot.getPortIndexFromIccId(uniqueId)); - } - } - value.put(SubscriptionManager.ALLOWED_NETWORK_TYPES, - "user=" + RadioAccessFamily.getRafFromNetworkType( - RILConstants.PREFERRED_NETWORK_MODE)); - - value.put(SubscriptionManager.USAGE_SETTING, - SubscriptionManager.USAGE_SETTING_UNKNOWN); - - Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - return uri; - } - - /** - * Generate and set carrier text based on input parameters - * @param showPlmn flag to indicate if plmn should be included in carrier text - * @param plmn plmn to be included in carrier text - * @param showSpn flag to indicate if spn should be included in carrier text - * @param spn spn to be included in carrier text - * @return true if carrier text is set, false otherwise - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn, - String spn) { - synchronized (mLock) { - int subId = getSubId(slotIndex); - if (mContext.getPackageManager().resolveContentProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null || - !SubscriptionManager.isValidSubscriptionId(subId)) { - // No place to store this info. Notify registrants of the change anyway as they - // might retrieve the SPN/PLMN text from the SST sticky broadcast. - // TODO: This can be removed once SubscriptionController is not running on devices - // that don't need it, such as TVs. - if (DBG) logd("[setPlmnSpn] No valid subscription to store info"); - notifySubscriptionInfoChanged(); - return false; - } - String carrierText = ""; - if (showPlmn) { - carrierText = plmn; - if (showSpn) { - // Need to show both plmn and spn if both are not same. - if(!Objects.equals(spn, plmn)) { - String separator = mContext.getString( - com.android.internal.R.string.kg_text_message_separator).toString(); - carrierText = new StringBuilder().append(carrierText).append(separator) - .append(spn).toString(); - } - } - } else if (showSpn) { - carrierText = spn; - } - setCarrierText(carrierText, subId); - return true; - } - } - - /** - * Set carrier text by simInfo index - * @param text new carrier text - * @param subId the unique SubInfoRecord index in database - * @return the number of records updated - */ - private int setCarrierText(String text, int subId) { - if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId); - - enforceModifyPhoneState("setCarrierText"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - boolean update = true; - int result = 0; - SubscriptionInfo subInfo = getSubscriptionInfo(subId); - if (subInfo != null) { - update = !TextUtils.equals(text, subInfo.getCarrierName()); - } - if (update) { - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.CARRIER_NAME, text); - - result = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - } else { - if (DBG) logd("[setCarrierText]: no value update"); - } - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Set SIM color tint by simInfo index - * - * @param subId the unique SubInfoRecord index in database - * @param tint the tint color of the SIM - * - * @return the number of records updated - */ - @Override - public int setIconTint(int subId, int tint) { - if (DBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId); - - enforceModifyPhoneState("setIconTint"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - validateSubId(subId); - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.HUE, tint); - if (DBG) logd("[setIconTint]- tint:" + tint + " set"); - - int result = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * This is only for internal use and the returned priority is arbitrary. The idea is to give a - * higher value to name source that has higher priority to override other name sources. - * @param nameSource Source of display name - * @return int representing the priority. Higher value means higher priority. - */ - public static int getNameSourcePriority(@SimDisplayNameSource int nameSource) { - int index = Arrays.asList( - SubscriptionManager.NAME_SOURCE_CARRIER_ID, - SubscriptionManager.NAME_SOURCE_SIM_PNN, - SubscriptionManager.NAME_SOURCE_SIM_SPN, - SubscriptionManager.NAME_SOURCE_CARRIER, - SubscriptionManager.NAME_SOURCE_USER_INPUT // user has highest priority. - ).indexOf(nameSource); - return (index < 0) ? 0 : index; - } - - /** - * Validate whether the NAME_SOURCE_SIM_PNN, NAME_SOURCE_SIM_SPN and - * NAME_SOURCE_CARRIER exist or not. - */ - @VisibleForTesting - public boolean isExistingNameSourceStillValid(SubscriptionInfo subInfo) { - - int subId = subInfo.getSubscriptionId(); - int phoneId = getPhoneId(subInfo.getSubscriptionId()); - - Phone phone = PhoneFactory.getPhone(phoneId); - if (phone == null) { - return true; - } - - String spn; - - switch (subInfo.getDisplayNameSource()) { - case SubscriptionManager.NAME_SOURCE_SIM_PNN: - String pnn = phone.getPlmn(); - return !TextUtils.isEmpty(pnn); - case SubscriptionManager.NAME_SOURCE_SIM_SPN: - spn = getServiceProviderName(phoneId); - return !TextUtils.isEmpty(spn); - case SubscriptionManager.NAME_SOURCE_CARRIER: - // Can not validate eSIM since it should not override with a lower priority source - // if the name is actually coming from eSIM and not from carrier config. - if (subInfo.isEmbedded()) { - return true; - } - CarrierConfigManager configLoader = - mContext.getSystemService(CarrierConfigManager.class); - PersistableBundle config = - configLoader.getConfigForSubId(subId); - if (config == null) { - return true; - } - boolean isCarrierNameOverride = config.getBoolean( - CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false); - String carrierName = config.getString( - CarrierConfigManager.KEY_CARRIER_NAME_STRING); - spn = getServiceProviderName(phoneId); - return isCarrierNameOverride - || (TextUtils.isEmpty(spn) && !TextUtils.isEmpty(carrierName)); - case SubscriptionManager.NAME_SOURCE_CARRIER_ID: - case SubscriptionManager.NAME_SOURCE_USER_INPUT: - return true; - } - return false; - } - - @VisibleForTesting - public String getServiceProviderName(int phoneId) { - UiccProfile profile = mUiccController.getUiccProfileForPhone(phoneId); - if (profile == null) { - return null; - } - return profile.getServiceProviderName(); - } - - /** - * Set display name by simInfo index with name source - * @param displayName the display name of SIM card - * @param subId the unique SubInfoRecord index in database - * @param nameSource SIM display name source - * @return the number of records updated - */ - @Override - public int setDisplayNameUsingSrc(String displayName, int subId, - @SimDisplayNameSource int nameSource) { - if (DBG) { - logd("[setDisplayName]+ displayName:" + displayName + " subId:" + subId - + " nameSource:" + nameSource); - } - - enforceModifyPhoneState("setDisplayNameUsingSrc"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - validateSubId(subId); - List allSubInfo = getSubInfo(null, null); - // if there is no sub in the db, return 0 since subId does not exist in db - if (allSubInfo == null || allSubInfo.isEmpty()) return 0; - for (SubscriptionInfo subInfo : allSubInfo) { - int subInfoNameSource = subInfo.getDisplayNameSource(); - boolean isHigherPriority = (getNameSourcePriority(subInfoNameSource) - > getNameSourcePriority(nameSource)); - boolean isEqualPriorityAndName = (getNameSourcePriority(subInfoNameSource) - == getNameSourcePriority(nameSource)) - && (TextUtils.equals(displayName, subInfo.getDisplayName())); - if (subInfo.getSubscriptionId() == subId - && isExistingNameSourceStillValid(subInfo) - && (isHigherPriority || isEqualPriorityAndName)) { - logd("Name source " + subInfoNameSource + "'s priority " - + getNameSourcePriority(subInfoNameSource) + " is greater than " - + "name source " + nameSource + "'s priority " - + getNameSourcePriority(nameSource) + ", return now."); - return 0; - } - } - String nameToSet; - if (TextUtils.isEmpty(displayName) || displayName.trim().length() == 0) { - nameToSet = mTelephonyManager.getSimOperatorName(subId); - if (TextUtils.isEmpty(nameToSet)) { - if (nameSource == SubscriptionManager.NAME_SOURCE_USER_INPUT - && SubscriptionManager.isValidSlotIndex(getSlotIndex(subId))) { - nameToSet = "CARD " + (getSlotIndex(subId) + 1); - } else { - nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES); - } - } - } else { - nameToSet = displayName; - } - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.DISPLAY_NAME, nameToSet); - if (nameSource >= SubscriptionManager.NAME_SOURCE_CARRIER_ID) { - if (DBG) logd("Set nameSource=" + nameSource); - value.put(SubscriptionManager.NAME_SOURCE, nameSource); - } - if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set"); - - // Update the nickname on the eUICC chip if it's an embedded subscription. - SubscriptionInfo sub = getSubscriptionInfo(subId); - if (sub != null && sub.isEmbedded()) { - // Ignore the result. - int cardId = sub.getCardId(); - if (DBG) logd("Updating embedded sub nickname on cardId: " + cardId); - EuiccManager euiccManager = ((EuiccManager) - mContext.getSystemService(Context.EUICC_SERVICE)).createForCardId(cardId); - euiccManager.updateSubscriptionNickname(subId, displayName, - // This PendingIntent simply fulfills the requirement to pass in a callback; - // we don't care about the result (hence 0 requestCode and no action - // specified on the intent). - PendingIntent.getService( - mContext, 0 /* requestCode */, new Intent(), - PendingIntent.FLAG_IMMUTABLE /* flags */)); - } - - int result = updateDatabase(value, subId, true); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Set phone number by subId - * @param number the phone number of the SIM - * @param subId the unique SubInfoRecord index in database - * @return the number of records updated - */ - @Override - public int setDisplayNumber(String number, int subId) { - if (DBG) logd("[setDisplayNumber]+ subId:" + subId); - - enforceModifyPhoneState("setDisplayNumber"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - validateSubId(subId); - int result = 0; - int phoneId = getPhoneId(subId); - - if (number == null || phoneId < 0 || - phoneId >= mTelephonyManager.getPhoneCount()) { - if (DBG) logd("[setDisplayNumber]- fail"); - return -1; - } - boolean update = true; - SubscriptionInfo subInfo = getSubscriptionInfo(subId); - if (subInfo != null) { - update = !TextUtils.equals(subInfo.getNumber(), number); - } - if (update) { - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.NUMBER, number); - - // This function had a call to update number on the SIM (Phone.setLine1Number()) but - // that was removed as there doesn't seem to be a reason for that. If it is added - // back, watch out for deadlocks. - result = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - if (DBG) logd("[setDisplayNumber]- update result :" + result); - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - notifySubscriptionInfoChanged(); - } else { - if (DBG) logd("[setDisplayNumber]: no value update"); - } - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Set the EHPLMNs and HPLMNs associated with the subscription. - */ - public void setAssociatedPlmns(String[] ehplmns, String[] hplmns, int subId) { - if (DBG) logd("[setAssociatedPlmns]+ subId:" + subId); - - validateSubId(subId); - int phoneId = getPhoneId(subId); - - if (phoneId < 0 || phoneId >= mTelephonyManager.getPhoneCount()) { - if (DBG) logd("[setAssociatedPlmns]- fail"); - return; - } - - // remove trailing empty strings which will also get stripped from - // SubscriptionInfo.getEhplmns() and SubscriptionInfo.getHplmns() - String formattedEhplmns = ehplmns == null ? "" : - Arrays.stream(ehplmns).filter(s -> s != null && !s.isEmpty()) - .collect(Collectors.joining(",")); - String formattedHplmns = hplmns == null ? "" : - Arrays.stream(hplmns).filter(s -> s != null && !s.isEmpty()) - .collect(Collectors.joining(",")); - boolean noChange = false; - SubscriptionInfo subInfo = getSubscriptionInfo(subId); - if (subInfo != null) { - noChange = (ehplmns == null && subInfo.getEhplmns().isEmpty()) - || String.join(",", subInfo.getEhplmns()).equals(formattedEhplmns); - noChange = noChange && (hplmns == null && subInfo.getHplmns().isEmpty()) - || String.join(",", subInfo.getHplmns()).equals(formattedHplmns); - } - if (!noChange) { - ContentValues value = new ContentValues(2); - value.put(SubscriptionManager.EHPLMNS, formattedEhplmns); - value.put(SubscriptionManager.HPLMNS, formattedHplmns); - - int count = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - if (DBG) logd("[setAssociatedPlmns]- update result :" + count); - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - notifySubscriptionInfoChanged(); - } else { - if (DBG) logd("[setAssociatedPlmns]+ subId:" + subId + "no value update"); - } - } - - /** - * Set data roaming by simInfo index - * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming - * @param subId the unique SubInfoRecord index in database - * @return the number of records updated - */ - @Override - public int setDataRoaming(int roaming, int subId) { - if (DBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId); - - enforceModifyPhoneState("setDataRoaming"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - validateSubId(subId); - if (roaming < 0) { - if (DBG) logd("[setDataRoaming]- fail"); - return -1; - } - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.DATA_ROAMING, roaming); - if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set"); - - int result = updateDatabase(value, subId, true); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Set device to device status sharing preference - * @param sharing the sharing preference to set - * @param subId - * @return the number of records updated - */ - @Override - public int setDeviceToDeviceStatusSharing(int sharing, int subId) { - if (DBG) logd("[setDeviceToDeviceStatusSharing]- sharing:" + sharing + " subId:" + subId); - - enforceModifyPhoneState("setDeviceToDeviceStatusSharing"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - validateSubId(subId); - if (sharing < 0) { - if (DBG) logd("[setDeviceToDeviceStatusSharing]- fail"); - return -1; - } - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.D2D_STATUS_SHARING, sharing); - if (DBG) logd("[setDeviceToDeviceStatusSharing]- sharing:" + sharing + " set"); - - int result = updateDatabase(value, subId, true); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Set contacts that allow device to device status sharing. - * @param contacts contacts to set - * @param subscriptionId - * @return the number of records updated - */ - @Override - public int setDeviceToDeviceStatusSharingContacts(String contacts, int subscriptionId) { - if (DBG) { - logd("[setDeviceToDeviceStatusSharingContacts]- contacts:" + contacts - + " subId:" + subscriptionId); - } - - enforceModifyPhoneState("setDeviceToDeviceStatusSharingContacts"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - validateSubId(subscriptionId); - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.D2D_STATUS_SHARING_SELECTED_CONTACTS, contacts); - if (DBG) { - logd("[setDeviceToDeviceStatusSharingContacts]- contacts:" + contacts - + " set"); - } - - int result = updateDatabase(value, subscriptionId, true); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - public void syncGroupedSetting(int refSubId) { - logd("syncGroupedSetting"); - try (Cursor cursor = mContext.getContentResolver().query( - SubscriptionManager.CONTENT_URI, null, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?", - new String[] {String.valueOf(refSubId)}, null)) { - if (cursor == null || !cursor.moveToFirst()) { - logd("[syncGroupedSetting] failed. Can't find refSubId " + refSubId); - return; - } - - ContentValues values = new ContentValues(GROUP_SHARING_PROPERTIES.size()); - for (String propKey : GROUP_SHARING_PROPERTIES) { - copyDataFromCursorToContentValue(propKey, cursor, values); - } - updateDatabase(values, refSubId, true); - } - } - - private void copyDataFromCursorToContentValue(String propKey, Cursor cursor, - ContentValues values) { - int columnIndex = cursor.getColumnIndex(propKey); - if (columnIndex == -1) { - logd("[copyDataFromCursorToContentValue] can't find column " + propKey); - return; - } - - switch (propKey) { - case SubscriptionManager.ENHANCED_4G_MODE_ENABLED: - case SubscriptionManager.VT_IMS_ENABLED: - case SubscriptionManager.WFC_IMS_ENABLED: - case SubscriptionManager.WFC_IMS_MODE: - case SubscriptionManager.WFC_IMS_ROAMING_MODE: - case SubscriptionManager.WFC_IMS_ROAMING_ENABLED: - case SubscriptionManager.DATA_ROAMING: - case SubscriptionManager.IMS_RCS_UCE_ENABLED: - case SubscriptionManager.CROSS_SIM_CALLING_ENABLED: - case SubscriptionManager.NR_ADVANCED_CALLING_ENABLED: - case SubscriptionManager.USER_HANDLE: - values.put(propKey, cursor.getInt(columnIndex)); - break; - case SubscriptionManager.DISPLAY_NAME: - case SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES: - values.put(propKey, cursor.getString(columnIndex)); - break; - default: - loge("[copyDataFromCursorToContentValue] invalid propKey " + propKey); - } - } - - // TODO: replace all updates with this helper method. - private int updateDatabase(ContentValues value, int subId, boolean updateEntireGroup) { - List infoList = getSubscriptionsInGroup(getGroupUuid(subId), - mContext.getOpPackageName(), mContext.getAttributionTag()); - if (!updateEntireGroup || infoList == null || infoList.size() == 0) { - // Only update specified subscriptions. - return mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - } else { - // Update all subscriptions in the same group. - int[] subIdList = new int[infoList.size()]; - for (int i = 0; i < infoList.size(); i++) { - subIdList[i] = infoList.get(i).getSubscriptionId(); - } - return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, - value, getSelectionForSubIdList(subIdList), null); - } - } - - /** - * Set carrier id by subId - * @param carrierId the subscription carrier id. - * @param subId the unique SubInfoRecord index in database - * @return the number of records updated - * - * @see TelephonyManager#getSimCarrierId() - */ - public int setCarrierId(int carrierId, int subId) { - if (DBG) logd("[setCarrierId]+ carrierId:" + carrierId + " subId:" + subId); - - enforceModifyPhoneState("setCarrierId"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - validateSubId(subId); - int result = 0; - boolean update = true; - SubscriptionInfo subInfo = getSubscriptionInfo(subId); - if (subInfo != null) { - update = subInfo.getCarrierId() != carrierId; - } - if (update) { - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.CARRIER_ID, carrierId); - result = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - } else { - if (DBG) logd("[setCarrierId]: no value update"); - } - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Set MCC/MNC by subscription ID - * @param mccMnc MCC/MNC associated with the subscription - * @param subId the unique SubInfoRecord index in database - * @return the number of records updated - */ - public int setMccMnc(String mccMnc, int subId) { - String mccString = mccMnc.substring(0, 3); - String mncString = mccMnc.substring(3); - int mcc = 0; - int mnc = 0; - try { - mcc = Integer.parseInt(mccString); - mnc = Integer.parseInt(mncString); - } catch (NumberFormatException e) { - loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc); - } - SubscriptionInfo subInfo = getSubscriptionInfo(subId); - // check if there are any update - boolean update = true; - if (subInfo != null) { - update = (subInfo.getMcc() != mcc) || (subInfo.getMnc() != mnc) - || !mccString.equals(subInfo.getMccString()) - || !mncString.equals(subInfo.getMncString()); - } - int result = 0; - if (update) { - ContentValues value = new ContentValues(4); - value.put(SubscriptionManager.MCC, mcc); - value.put(SubscriptionManager.MNC, mnc); - value.put(SubscriptionManager.MCC_STRING, mccString); - value.put(SubscriptionManager.MNC_STRING, mncString); - - result = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId); - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - notifySubscriptionInfoChanged(); - } else { - if (DBG) logd("[setMccMnc] - no values update"); - } - return result; - } - - /** - * Scrub given IMSI on production builds. - */ - private String scrubImsi(String imsi) { - if (Build.IS_ENG) { - return imsi; - } else if (imsi != null) { - return imsi.substring(0, Math.min(6, imsi.length())) + "..."; - } else { - return "null"; - } - } - - /** - * Set IMSI by subscription ID - * @param imsi IMSI (International Mobile Subscriber Identity) - * @return the number of records updated - */ - public int setImsi(String imsi, int subId) { - if (DBG) logd("[setImsi]+ imsi:" + scrubImsi(imsi) + " subId:" + subId); - boolean update = true; - int result = 0; - SubscriptionInfo subInfo = getSubscriptionInfo(subId); - if (subInfo != null) { - update = !TextUtils.equals(getImsiPrivileged(subId),imsi); - } - - if (update) { - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.IMSI, imsi); - result = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - } else { - if (DBG) logd("[setImsi]: no value update"); - } - return result; - } - - /** - * Set uicc applications being enabled or disabled. - * @param enabled whether uicc applications are enabled or disabled. - */ - public void setUiccApplicationsEnabled(boolean enabled, int subId) { - if (DBG) logd("[setUiccApplicationsEnabled]+ enabled:" + enabled + " subId:" + subId); - - enforceModifyPhoneState("setUiccApplicationsEnabled"); - - long identity = Binder.clearCallingIdentity(); - try { - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, enabled); - - mContext.getContentResolver().update(SubscriptionManager.getUriForSubscriptionId(subId), - value, null, null); - - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifyUiccAppsEnableChanged(); - notifySubscriptionInfoChanged(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Register to change of uicc applications enablement changes. - * @param notifyNow whether to notify target upon registration. - */ - public void registerForUiccAppsEnabled(Handler handler, int what, Object object, - boolean notifyNow) { - mUiccAppsEnableChangeRegList.addUnique(handler, what, object); - if (notifyNow) { - handler.obtainMessage(what, object).sendToTarget(); - } - } - - /** - * Unregister to change of uicc applications enablement changes. - */ - public void unregisterForUiccAppsEnabled(Handler handler) { - mUiccAppsEnableChangeRegList.remove(handler); - } - - private void notifyUiccAppsEnableChanged() { - mUiccAppsEnableChangeRegList.notifyRegistrants(); - } - - /** - * Get IMSI by subscription ID - * For active subIds, this will always return the corresponding imsi - * For inactive subIds, once they are activated once, even if they are deactivated at the time - * of calling this function, the corresponding imsi will be returned - * When calling this method, the permission check should have already been done to allow - * only privileged read - * - * @return imsi - */ - public String getImsiPrivileged(int subId) { - try (Cursor cursor = mContext.getContentResolver().query( - SubscriptionManager.CONTENT_URI, null, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?", - new String[] {String.valueOf(subId)}, null)) { - String imsi = null; - if (cursor != null) { - if (cursor.moveToNext()) { - imsi = getOptionalStringFromCursor(cursor, SubscriptionManager.IMSI, - /*defaultVal*/ null); - } - } else { - logd("getImsiPrivileged: failed to retrieve imsi."); - } - - return imsi; - } - } - - /** - * Set ISO country code by subscription ID - * @param iso iso country code associated with the subscription - * @param subId the unique SubInfoRecord index in database - * @return the number of records updated - */ - public int setCountryIso(String iso, int subId) { - if (DBG) logd("[setCountryIso]+ iso:" + iso + " subId:" + subId); - boolean update = true; - int result = 0; - SubscriptionInfo subInfo = getSubscriptionInfo(subId); - if (subInfo != null) { - update = !TextUtils.equals(subInfo.getCountryIso(), iso); - } - if (update) { - ContentValues value = new ContentValues(); - value.put(SubscriptionManager.ISO_COUNTRY_CODE, iso); - - result = mContext.getContentResolver().update( - SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - } else { - if (DBG) logd("[setCountryIso]: no value update"); - } - return result; - } - - @Override - public int getSlotIndex(int subId) { - if (VDBG) printStackTrace("[getSlotIndex] subId=" + subId); - - if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - subId = getDefaultSubId(); - } - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - if (DBG) logd("[getSlotIndex]- subId invalid"); - return SubscriptionManager.INVALID_SIM_SLOT_INDEX; - } - - int size = mSlotIndexToSubIds.size(); - - if (size == 0) { - if (DBG) logd("[getSlotIndex]- size == 0, return SIM_NOT_INSERTED instead"); - return SubscriptionManager.SIM_NOT_INSERTED; - } - - for (Entry> entry : mSlotIndexToSubIds.entrySet()) { - int sim = entry.getKey(); - ArrayList subs = entry.getValue(); - - if (subs != null && subs.contains(subId)) { - if (VDBG) logv("[getSlotIndex]- return = " + sim); - return sim; - } - } - - if (DBG) logd("[getSlotIndex]- return fail"); - return SubscriptionManager.INVALID_SIM_SLOT_INDEX; - } - - /** - * Return the subIds for specified slot Id. - * @deprecated - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Deprecated - public int[] getSubIds(int slotIndex) { - if (VDBG) printStackTrace("[getSubId]+ slotIndex=" + slotIndex); - - // Map default slotIndex to the current default subId. - // TODO: Not used anywhere sp consider deleting as it's somewhat nebulous - // as a slot maybe used for multiple different type of "connections" - // such as: voice, data and sms. But we're doing the best we can and using - // getDefaultSubId which makes a best guess. - if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) { - slotIndex = getSlotIndex(getDefaultSubId()); - if (VDBG) logd("[getSubId] map default slotIndex=" + slotIndex); - } - - // Check that we have a valid slotIndex or the slotIndex is for a remote SIM (remote SIM - // uses special slot index that may be invalid otherwise) - if (!SubscriptionManager.isValidSlotIndex(slotIndex) - && slotIndex != SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB) { - if (DBG) logd("[getSubId]- invalid slotIndex=" + slotIndex); - return null; - } - - // Check if we've got any SubscriptionInfo records using slotIndexToSubId as a surrogate. - int size = mSlotIndexToSubIds.size(); - if (size == 0) { - if (VDBG) { - logd("[getSubId]- sSlotIndexToSubIds.size == 0, return null slotIndex=" - + slotIndex); - } - return null; - } - - // Convert ArrayList to array - ArrayList subIds = mSlotIndexToSubIds.getCopy(slotIndex); - if (subIds != null && subIds.size() > 0) { - int[] subIdArr = new int[subIds.size()]; - for (int i = 0; i < subIds.size(); i++) { - subIdArr[i] = subIds.get(i); - } - if (VDBG) logd("[getSubId]- subIdArr=" + Arrays.toString(subIdArr)); - return subIdArr; - } else { - if (DBG) logd("[getSubId]- numSubIds == 0, return null slotIndex=" + slotIndex); - return null; - } - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public int getPhoneId(int subId) { - if (VDBG) printStackTrace("[getPhoneId] subId=" + subId); - int phoneId; - - if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - subId = getDefaultSubId(); - if (DBG) logd("[getPhoneId] asked for default subId=" + subId); - } - - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - if (VDBG) { - logdl("[getPhoneId]- invalid subId return=" - + SubscriptionManager.INVALID_PHONE_INDEX); - } - return SubscriptionManager.INVALID_PHONE_INDEX; - } - - int size = mSlotIndexToSubIds.size(); - if (size == 0) { - phoneId = mDefaultPhoneId; - if (VDBG) logdl("[getPhoneId]- no sims, returning default phoneId=" + phoneId); - return phoneId; - } - - // FIXME: Assumes phoneId == slotIndex - for (Entry> entry: mSlotIndexToSubIds.entrySet()) { - int sim = entry.getKey(); - ArrayList subs = entry.getValue(); - - if (subs != null && subs.contains(subId)) { - if (VDBG) logdl("[getPhoneId]- found subId=" + subId + " phoneId=" + sim); - return sim; - } - } - - phoneId = mDefaultPhoneId; - if (VDBG) { - logd("[getPhoneId]- subId=" + subId + " not found return default phoneId=" + phoneId); - } - return phoneId; - - } - - /** - * @return the number of records cleared - */ - public int clearSubInfo() { - enforceModifyPhoneState("clearSubInfo"); - - // Now that all security checks passes, perform the operation as ourselves. - final long identity = Binder.clearCallingIdentity(); - try { - int size = mSlotIndexToSubIds.size(); - - if (size == 0) { - if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size); - return 0; - } - - mSlotIndexToSubIds.clear(); - if (DBG) logdl("[clearSubInfo]- clear size=" + size); - return size; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private void logvl(String msg) { - logv(msg); - mLocalLog.log(msg); - } - - private void logv(String msg) { - Rlog.v(LOG_TAG, msg); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected void logdl(String msg) { - logd(msg); - mLocalLog.log(msg); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private void logd(String msg) { - Rlog.d(LOG_TAG, msg); - } - - private void logel(String msg) { - loge(msg); - mLocalLog.log(msg); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private void loge(String msg) { - Rlog.e(LOG_TAG, msg); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public int getDefaultSubId() { - int subId; - boolean isVoiceCapable = mTelephonyManager.isVoiceCapable(); - if (isVoiceCapable) { - subId = getDefaultVoiceSubId(); - if (VDBG) logdl("[getDefaultSubId] isVoiceCapable subId=" + subId); - } else { - subId = getDefaultDataSubId(); - if (VDBG) logdl("[getDefaultSubId] NOT VoiceCapable subId=" + subId); - } - if (!isActiveSubId(subId)) { - subId = mDefaultFallbackSubId.get(); - if (VDBG) logdl("[getDefaultSubId] NOT active use fall back subId=" + subId); - } - if (VDBG) logv("[getDefaultSubId]- value = " + subId); - return subId; - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public void setDefaultSmsSubId(int subId) { - enforceModifyPhoneState("setDefaultSmsSubId"); - - if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID"); - } - if (DBG) logdl("[setDefaultSmsSubId] subId=" + subId); - setGlobalSetting(Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId); - broadcastDefaultSmsSubIdChanged(subId); - } - - private void broadcastDefaultSmsSubIdChanged(int subId) { - // Broadcast an Intent for default sms sub change - if (DBG) logdl("[broadcastDefaultSmsSubIdChanged] subId=" + subId); - Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - SubscriptionManager.putSubscriptionIdExtra(intent, subId); - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public int getDefaultSmsSubId() { - int subId = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - if (VDBG) logd("[getDefaultSmsSubId] subId=" + subId); - return subId; - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public void setDefaultVoiceSubId(int subId) { - enforceModifyPhoneState("setDefaultVoiceSubId"); - - if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID"); - } - - logdl("[setDefaultVoiceSubId] subId=" + subId); - - int previousDefaultSub = getDefaultSubId(); - - setGlobalSetting(Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId); - broadcastDefaultVoiceSubIdChanged(subId); - - PhoneAccountHandle newHandle = - subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID - ? null : mTelephonyManager.getPhoneAccountHandleForSubscriptionId( - subId); - - TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); - - telecomManager.setUserSelectedOutgoingPhoneAccount(newHandle); - logd("[setDefaultVoiceSubId] requesting change to phoneAccountHandle=" + newHandle); - - if (previousDefaultSub != getDefaultSubId()) { - sendDefaultChangedBroadcast(getDefaultSubId()); - logd(String.format("[setDefaultVoiceSubId] change to subId=%d", getDefaultSubId())); - } else { - logd(String.format("[setDefaultVoiceSubId] default subId not changed. subId=%d", - previousDefaultSub)); - } - } - - /** - * Broadcast intent of ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED. - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public void broadcastDefaultVoiceSubIdChanged(int subId) { - // Broadcast an Intent for default voice sub change - if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId); - Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - SubscriptionManager.putSubscriptionIdExtra(intent, subId); - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public int getDefaultVoiceSubId() { - int subId = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId); - return subId; - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public int getDefaultDataSubId() { - int subId = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - if (VDBG) logd("[getDefaultDataSubId] subId=" + subId); - return subId; - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Override - public void setDefaultDataSubId(int subId) { - enforceModifyPhoneState("setDefaultDataSubId"); - - final long identity = Binder.clearCallingIdentity(); - try { - if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID"); - } - - ProxyController proxyController = ProxyController.getInstance(); - int len = TelephonyManager.from(mContext).getActiveModemCount(); - logdl("[setDefaultDataSubId] num phones=" + len + ", subId=" + subId); - - if (SubscriptionManager.isValidSubscriptionId(subId)) { - // Only re-map modems if the new default data sub is valid - RadioAccessFamily[] rafs = new RadioAccessFamily[len]; - boolean atLeastOneMatch = false; - for (int phoneId = 0; phoneId < len; phoneId++) { - Phone phone = PhoneFactory.getPhone(phoneId); - int raf; - int id = phone.getSubId(); - if (id == subId) { - // TODO Handle the general case of N modems and M subscriptions. - raf = proxyController.getMaxRafSupported(); - atLeastOneMatch = true; - } else { - // TODO Handle the general case of N modems and M subscriptions. - raf = proxyController.getMinRafSupported(); - } - logdl("[setDefaultDataSubId] phoneId=" + phoneId + " subId=" + id + " RAF=" - + raf); - rafs[phoneId] = new RadioAccessFamily(phoneId, raf); - } - if (atLeastOneMatch) { - proxyController.setRadioCapability(rafs); - } else { - if (DBG) logdl("[setDefaultDataSubId] no valid subId's found - not updating."); - } - } - - int previousDefaultSub = getDefaultSubId(); - setGlobalSetting(Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId); - MultiSimSettingController.getInstance().notifyDefaultDataSubChanged(); - broadcastDefaultDataSubIdChanged(subId); - if (previousDefaultSub != getDefaultSubId()) { - sendDefaultChangedBroadcast(getDefaultSubId()); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private void broadcastDefaultDataSubIdChanged(int subId) { - // Broadcast an Intent for default data sub change - if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId); - Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - SubscriptionManager.putSubscriptionIdExtra(intent, subId); - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - /* Sets the default subscription. If only one sub is active that - * sub is set as default subId. If two or more sub's are active - * the first sub is set as default subscription - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected void setDefaultFallbackSubId(int subId, int subscriptionType) { - if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID"); - } - if (DBG) { - logdl("[setDefaultFallbackSubId] subId=" + subId + ", subscriptionType=" - + subscriptionType); - } - int previousDefaultSub = getDefaultSubId(); - if (isSubscriptionForRemoteSim(subscriptionType)) { - mDefaultFallbackSubId.set(subId); - return; - } - if (SubscriptionManager.isValidSubscriptionId(subId)) { - int phoneId = getPhoneId(subId); - if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount() - || mTelephonyManager.getSimCount() == 1)) { - if (DBG) logdl("[setDefaultFallbackSubId] set sDefaultFallbackSubId=" + subId); - mDefaultFallbackSubId.set(subId); - // Update MCC MNC device configuration information - String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId); - MccTable.updateMccMncConfiguration(mContext, defaultMccMnc); - } else { - if (DBG) { - logdl("[setDefaultFallbackSubId] not set invalid phoneId=" + phoneId - + " subId=" + subId); - } - } - } - if (previousDefaultSub != getDefaultSubId()) { - sendDefaultChangedBroadcast(getDefaultSubId()); - } - } - - public void sendDefaultChangedBroadcast(int subId) { - // Broadcast an Intent for default sub change - int phoneId = SubscriptionManager.getPhoneId(subId); - Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId); - if (DBG) { - logdl("[sendDefaultChangedBroadcast] broadcast default subId changed phoneId=" - + phoneId + " subId=" + subId); - } - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - /** - * Whether a subscription is opportunistic or not. - */ - public boolean isOpportunistic(int subId) { - SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName(), - mContext.getAttributionTag()); - return (info != null) && info.isOpportunistic(); - } - - // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true - // when there are multiple subscriptions per sim and probably for other reasons. - public int getSubId(int phoneId) { - int[] subIds = getSubIds(phoneId); - if (subIds == null || subIds.length == 0) { - return SubscriptionManager.INVALID_SUBSCRIPTION_ID; - } - return subIds[0]; - } - - /** Must be public for access from instrumentation tests. */ - @VisibleForTesting - public List getSubInfoUsingSlotIndexPrivileged(int slotIndex) { - if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]+ slotIndex:" + slotIndex); - if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) { - slotIndex = getSlotIndex(getDefaultSubId()); - } - if (!SubscriptionManager.isValidSlotIndex(slotIndex)) { - if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]- invalid slotIndex"); - return null; - } - - Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI, - null, SubscriptionManager.SIM_SLOT_INDEX + "=?", - new String[]{String.valueOf(slotIndex)}, null); - ArrayList subList = null; - try { - if (cursor != null) { - while (cursor.moveToNext()) { - SubscriptionInfo subInfo = getSubInfoRecord(cursor); - if (subInfo != null) { - if (subList == null) { - subList = new ArrayList(); - } - subList.add(subInfo); - } - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - if (DBG) logd("[getSubInfoUsingSlotIndex]- null info return"); - - return subList; - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private void validateSubId(int subId) { - if (DBG) logd("validateSubId subId: " + subId); - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - throw new IllegalArgumentException("Invalid sub id passed as parameter"); - } else if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - throw new IllegalArgumentException("Default sub id passed as parameter"); - } - } - - private synchronized ArrayList getActiveSubIdArrayList() { - // Clone the sub id list so it can't change out from under us while iterating - List>> simInfoList = - new ArrayList<>(mSlotIndexToSubIds.entrySet()); - - // Put the set of sub ids in slot index order - Collections.sort(simInfoList, (x, y) -> x.getKey().compareTo(y.getKey())); - - // Collect the sub ids for each slot in turn - ArrayList allSubs = new ArrayList<>(); - for (Entry> slot : simInfoList) { - allSubs.addAll(slot.getValue()); - } - return allSubs; - } - - private boolean isSubscriptionVisible(int subId) { - synchronized (mSubInfoListLock) { - for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) { - if (info.getSubscriptionId() == subId) { - // If group UUID is null, it's stand alone opportunistic profile. So it's - // visible. Otherwise, it's bundled opportunistic profile, and is not visible. - return info.getGroupUuid() == null; - } - } - } - - return true; - } - - /** - * @return the list of subId's that are active, is never null but the length maybe 0. - */ - @Override - public int[] getActiveSubIdList(boolean visibleOnly) { - enforceReadPrivilegedPhoneState("getActiveSubIdList"); - - final long token = Binder.clearCallingIdentity(); - try { - List allSubs = getActiveSubIdArrayList(); - - if (visibleOnly) { - // Grouped opportunistic subscriptions should be hidden. - allSubs = allSubs.stream().filter(subId -> isSubscriptionVisible(subId)) - .collect(Collectors.toList()); - } - - int[] subIdArr = new int[allSubs.size()]; - int i = 0; - for (int sub : allSubs) { - subIdArr[i] = sub; - i++; - } - - if (VDBG) { - logdl("[getActiveSubIdList] allSubs=" + allSubs + " subIdArr.length=" - + subIdArr.length); - } - return subIdArr; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public boolean isActiveSubId(int subId, String callingPackage, String callingFeatureId) { - if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage, - callingFeatureId, "isActiveSubId")) { - throw new SecurityException("Requires READ_PHONE_STATE permission."); - } - final long identity = Binder.clearCallingIdentity(); - try { - return isActiveSubId(subId); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Deprecated // This should be moved into isActiveSubId(int, String) - public boolean isActiveSubId(int subId) { - boolean retVal = SubscriptionManager.isValidSubscriptionId(subId) - && getActiveSubIdArrayList().contains(subId); - - if (VDBG) logdl("[isActiveSubId]- " + retVal); - return retVal; - } - - /** - * Store properties associated with SubscriptionInfo in database - * @param subId Subscription Id of Subscription - * @param propKey Column name in database associated with SubscriptionInfo - * @param propValue Value to store in DB for particular subId & column name - * - * @return number of rows updated. - * @hide - */ - @Override - public int setSubscriptionProperty(int subId, String propKey, String propValue) { - enforceModifyPhoneState("setSubscriptionProperty"); - final long token = Binder.clearCallingIdentity(); - - try { - validateSubId(subId); - ContentResolver resolver = mContext.getContentResolver(); - int result = setSubscriptionPropertyIntoContentResolver( - subId, propKey, propValue, resolver); - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - - return result; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private int setSubscriptionPropertyIntoContentResolver( - int subId, String propKey, String propValue, ContentResolver resolver) { - ContentValues value = new ContentValues(); - boolean updateEntireGroup = GROUP_SHARING_PROPERTIES.contains(propKey); - switch (propKey) { - case SubscriptionManager.CB_EXTREME_THREAT_ALERT: - case SubscriptionManager.CB_SEVERE_THREAT_ALERT: - case SubscriptionManager.CB_AMBER_ALERT: - case SubscriptionManager.CB_EMERGENCY_ALERT: - case SubscriptionManager.CB_ALERT_SOUND_DURATION: - case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL: - case SubscriptionManager.CB_ALERT_VIBRATE: - case SubscriptionManager.CB_ALERT_SPEECH: - case SubscriptionManager.CB_ETWS_TEST_ALERT: - case SubscriptionManager.CB_CHANNEL_50_ALERT: - case SubscriptionManager.CB_CMAS_TEST_ALERT: - case SubscriptionManager.CB_OPT_OUT_DIALOG: - case SubscriptionManager.ENHANCED_4G_MODE_ENABLED: - case SubscriptionManager.IS_OPPORTUNISTIC: - case SubscriptionManager.VT_IMS_ENABLED: - case SubscriptionManager.WFC_IMS_ENABLED: - case SubscriptionManager.WFC_IMS_MODE: - case SubscriptionManager.WFC_IMS_ROAMING_MODE: - case SubscriptionManager.WFC_IMS_ROAMING_ENABLED: - case SubscriptionManager.IMS_RCS_UCE_ENABLED: - case SubscriptionManager.CROSS_SIM_CALLING_ENABLED: - case SubscriptionManager.VOIMS_OPT_IN_STATUS: - case SubscriptionManager.NR_ADVANCED_CALLING_ENABLED: - case SubscriptionManager.USAGE_SETTING: - case SubscriptionManager.USER_HANDLE: - case SubscriptionManager.SATELLITE_ENABLED: - value.put(propKey, Integer.parseInt(propValue)); - break; - case SubscriptionManager.ALLOWED_NETWORK_TYPES: - case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER: - case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS: - value.put(propKey, propValue); - break; - default: - if (DBG) logd("Invalid column name"); - break; - } - - return updateDatabase(value, subId, updateEntireGroup); - } - - /** - * Get properties associated with SubscriptionInfo from database - * - * @param subId Subscription Id of Subscription - * @param propKey Column name in SubscriptionInfo database - * @return Value associated with subId and propKey column in database - */ - @Override - public String getSubscriptionProperty(int subId, String propKey, String callingPackage, - String callingFeatureId) { - switch (propKey) { - case SubscriptionManager.GROUP_UUID: - if (mContext.checkCallingOrSelfPermission( - Manifest.permission.READ_PRIVILEGED_PHONE_STATE) != PERMISSION_GRANTED) { - EventLog.writeEvent(0x534e4554, "213457638", Binder.getCallingUid()); - return null; - } - break; - default: - if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, - callingPackage, callingFeatureId, "getSubscriptionProperty")) { - return null; - } - } - - final long identity = Binder.clearCallingIdentity(); - try { - return getSubscriptionProperty(subId, propKey); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Get properties associated with SubscriptionInfo from database. Note this is the version - * without permission check for telephony internal use only. - * - * @param subId Subscription Id of Subscription - * @param propKey Column name in SubscriptionInfo database - * @return Value associated with subId and propKey column in database - */ - public String getSubscriptionProperty(int subId, String propKey) { - String resultValue = null; - try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI, - new String[]{propKey}, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?", - new String[]{subId + ""}, null)) { - if (cursor != null) { - if (cursor.moveToFirst()) { - switch (propKey) { - case SubscriptionManager.CB_EXTREME_THREAT_ALERT: - case SubscriptionManager.CB_SEVERE_THREAT_ALERT: - case SubscriptionManager.CB_AMBER_ALERT: - case SubscriptionManager.CB_EMERGENCY_ALERT: - case SubscriptionManager.CB_ALERT_SOUND_DURATION: - case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL: - case SubscriptionManager.CB_ALERT_VIBRATE: - case SubscriptionManager.CB_ALERT_SPEECH: - case SubscriptionManager.CB_ETWS_TEST_ALERT: - case SubscriptionManager.CB_CHANNEL_50_ALERT: - case SubscriptionManager.CB_CMAS_TEST_ALERT: - case SubscriptionManager.CB_OPT_OUT_DIALOG: - case SubscriptionManager.ENHANCED_4G_MODE_ENABLED: - case SubscriptionManager.VT_IMS_ENABLED: - case SubscriptionManager.WFC_IMS_ENABLED: - case SubscriptionManager.WFC_IMS_MODE: - case SubscriptionManager.WFC_IMS_ROAMING_MODE: - case SubscriptionManager.WFC_IMS_ROAMING_ENABLED: - case SubscriptionManager.IMS_RCS_UCE_ENABLED: - case SubscriptionManager.CROSS_SIM_CALLING_ENABLED: - case SubscriptionManager.IS_OPPORTUNISTIC: - case SubscriptionManager.GROUP_UUID: - case SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES: - case SubscriptionManager.ALLOWED_NETWORK_TYPES: - case SubscriptionManager.D2D_STATUS_SHARING: - case SubscriptionManager.VOIMS_OPT_IN_STATUS: - case SubscriptionManager.D2D_STATUS_SHARING_SELECTED_CONTACTS: - case SubscriptionManager.NR_ADVANCED_CALLING_ENABLED: - case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER: - case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS: - case SubscriptionManager.USAGE_SETTING: - case SubscriptionManager.USER_HANDLE: - case SubscriptionManager.SATELLITE_ENABLED: - resultValue = cursor.getString(0); - break; - default: - if(DBG) logd("Invalid column name"); - break; - } - } else { - if(DBG) logd("Valid row not present in db"); - } - } else { - if(DBG) logd("Query failed"); - } - } - - if (DBG) logd("getSubscriptionProperty Query value = " + resultValue); - return resultValue; - } - - private void printStackTrace(String msg) { - RuntimeException re = new RuntimeException(); - logd("StackTrace - " + msg); - StackTraceElement[] st = re.getStackTrace(); - boolean first = true; - for (StackTraceElement ste : st) { - if (first) { - first = false; - } else { - logd(ste.toString()); - } - } - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, - "Requires DUMP"); - final long token = Binder.clearCallingIdentity(); - try { - pw.println("SubscriptionController:"); - pw.println(" mLastISubServiceRegTime=" + mLastISubServiceRegTime); - pw.println(" defaultSubId=" + getDefaultSubId()); - pw.println(" defaultDataSubId=" + getDefaultDataSubId()); - pw.println(" defaultVoiceSubId=" + getDefaultVoiceSubId()); - pw.println(" defaultSmsSubId=" + getDefaultSmsSubId()); - pw.println(" defaultVoicePhoneId=" + SubscriptionManager.getDefaultVoicePhoneId()); - pw.flush(); - - for (Entry> entry : mSlotIndexToSubIds.entrySet()) { - pw.println(" sSlotIndexToSubId[" + entry.getKey() + "]: subIds=" + entry); - } - pw.flush(); - pw.println("++++++++++++++++++++++++++++++++"); - - List sirl = getActiveSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - if (sirl != null) { - pw.println(" ActiveSubInfoList:"); - for (SubscriptionInfo entry : sirl) { - pw.println(" " + entry.toString()); - } - } else { - pw.println(" ActiveSubInfoList: is null"); - } - pw.flush(); - pw.println("++++++++++++++++++++++++++++++++"); - - sirl = getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag()); - if (sirl != null) { - pw.println(" AllSubInfoList:"); - for (SubscriptionInfo entry : sirl) { - pw.println(" " + entry.toString()); - } - } else { - pw.println(" AllSubInfoList: is null"); - } - pw.flush(); - pw.println("++++++++++++++++++++++++++++++++"); - - mLocalLog.dump(fd, pw, args); - pw.flush(); - pw.println("++++++++++++++++++++++++++++++++"); - pw.flush(); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Migrating Ims settings from global setting to subscription DB, if not already done. - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public void migrateImsSettings() { - migrateImsSettingHelper( - Settings.Global.ENHANCED_4G_MODE_ENABLED, - SubscriptionManager.ENHANCED_4G_MODE_ENABLED); - migrateImsSettingHelper( - Settings.Global.VT_IMS_ENABLED, - SubscriptionManager.VT_IMS_ENABLED); - migrateImsSettingHelper( - Settings.Global.WFC_IMS_ENABLED, - SubscriptionManager.WFC_IMS_ENABLED); - migrateImsSettingHelper( - Settings.Global.WFC_IMS_MODE, - SubscriptionManager.WFC_IMS_MODE); - migrateImsSettingHelper( - Settings.Global.WFC_IMS_ROAMING_MODE, - SubscriptionManager.WFC_IMS_ROAMING_MODE); - migrateImsSettingHelper( - Settings.Global.WFC_IMS_ROAMING_ENABLED, - SubscriptionManager.WFC_IMS_ROAMING_ENABLED); - } - - private void migrateImsSettingHelper(String settingGlobal, String subscriptionProperty) { - ContentResolver resolver = mContext.getContentResolver(); - int defaultSubId = getDefaultVoiceSubId(); - if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - return; - } - try { - int prevSetting = Settings.Global.getInt(resolver, settingGlobal); - - if (prevSetting != DEPRECATED_SETTING) { - // Write previous setting into Subscription DB. - setSubscriptionPropertyIntoContentResolver(defaultSubId, subscriptionProperty, - Integer.toString(prevSetting), resolver); - // Write global setting value with DEPRECATED_SETTING making sure - // migration only happen once. - Settings.Global.putInt(resolver, settingGlobal, DEPRECATED_SETTING); - } - } catch (Settings.SettingNotFoundException e) { - } - } - - /** - * Set whether a subscription is opportunistic. - * - * Throws SecurityException if doesn't have required permission. - * - * @param opportunistic whether it’s opportunistic subscription. - * @param subId the unique SubscriptionInfo index in database - * @param callingPackage The package making the IPC. - * @return the number of records updated - */ - @Override - public int setOpportunistic(boolean opportunistic, int subId, String callingPackage) { - try { - TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege( - mContext, subId, callingPackage); - } catch (SecurityException e) { - // The subscription may be inactive eSIM profile. If so, check the access rule in - // database. - enforceCarrierPrivilegeOnInactiveSub(subId, callingPackage, - "Caller requires permission on sub " + subId); - } - - long token = Binder.clearCallingIdentity(); - try { - int ret = setSubscriptionProperty(subId, SubscriptionManager.IS_OPPORTUNISTIC, - String.valueOf(opportunistic ? 1 : 0)); - if (ret != 0) notifySubscriptionInfoChanged(); - return ret; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Get subscription info from database, and check whether caller has carrier privilege - * permission with it. If checking fails, throws SecurityException. - */ - private void enforceCarrierPrivilegeOnInactiveSub(int subId, String callingPackage, - String message) { - mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - - SubscriptionManager subManager = (SubscriptionManager) - mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - - List subInfo; - long token = Binder.clearCallingIdentity(); - try { - subInfo = getSubInfo( - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null); - } finally { - Binder.restoreCallingIdentity(token); - } - - try { - if (!isActiveSubId(subId) && subInfo != null && subInfo.size() == 1 - && subManager.canManageSubscription(subInfo.get(0), callingPackage)) { - return; - } - throw new SecurityException(message); - } catch (IllegalArgumentException e) { - // canManageSubscription will throw IllegalArgumentException if sub is not embedded - // or package name is unknown. In this case, we also see it as permission check failure - // and throw a SecurityException. - throw new SecurityException(message); - } - } - - @Override - public void setPreferredDataSubscriptionId(int subId, boolean needValidation, - ISetOpportunisticDataCallback callback) { - enforceModifyPhoneState("setPreferredDataSubscriptionId"); - final long token = Binder.clearCallingIdentity(); - - try { - PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance(); - if (phoneSwitcher == null) { - logd("Set preferred data sub: phoneSwitcher is null."); - AnomalyReporter.reportAnomaly( - UUID.fromString("a73fe57f-4178-4bc3-a7ae-9d7354939274"), - "Set preferred data sub: phoneSwitcher is null."); - if (callback != null) { - try { - callback.onComplete(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION); - } catch (RemoteException exception) { - logd("RemoteException " + exception); - } - } - return; - } - - phoneSwitcher.trySetOpportunisticDataSubscription(subId, needValidation, callback); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public int getPreferredDataSubscriptionId() { - enforceReadPrivilegedPhoneState("getPreferredDataSubscriptionId"); - final long token = Binder.clearCallingIdentity(); - - try { - PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance(); - if (phoneSwitcher == null) { - AnomalyReporter.reportAnomaly( - UUID.fromString("e72747ab-d0aa-4b0e-9dd5-cb99365c6d58"), - "Get preferred data sub: phoneSwitcher is null."); - return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; - } - - return phoneSwitcher.getAutoSelectedDataSubId(); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public List getOpportunisticSubscriptions(String callingPackage, - String callingFeatureId) { - return getSubscriptionInfoListFromCacheHelper(callingPackage, callingFeatureId, - makeCacheListCopyWithLock(mCacheOpportunisticSubInfoList)); - } - - /** - * Inform SubscriptionManager that subscriptions in the list are bundled - * as a group. Typically it's a primary subscription and an opportunistic - * subscription. It should only affect multi-SIM scenarios where primary - * and opportunistic subscriptions can be activated together. - * Being in the same group means they might be activated or deactivated - * together, some of them may be invisible to the users, etc. - * - * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} - * permission or had carrier privilege permission on the subscriptions: - * {@link TelephonyManager#hasCarrierPrivileges(int)} or - * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)} - * - * @throws SecurityException if the caller doesn't meet the requirements - * outlined above. - * @throws IllegalArgumentException if the some subscriptions in the list doesn't exist. - * - * @param subIdList list of subId that will be in the same group - * @return groupUUID a UUID assigned to the subscription group. It returns - * null if fails. - * - */ - @Override - public ParcelUuid createSubscriptionGroup(int[] subIdList, String callingPackage) { - if (subIdList == null || subIdList.length == 0) { - throw new IllegalArgumentException("Invalid subIdList " + Arrays.toString(subIdList)); - } - - // Makes sure calling package matches caller UID. - mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - // If it doesn't have modify phone state permission, or carrier privilege permission, - // a SecurityException will be thrown. - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList( - subIdList, callingPackage)) { - throw new SecurityException("CreateSubscriptionGroup needs MODIFY_PHONE_STATE or" - + " carrier privilege permission on all specified subscriptions"); - } - - long identity = Binder.clearCallingIdentity(); - - try { - // Generate a UUID. - ParcelUuid groupUUID = new ParcelUuid(UUID.randomUUID()); - - ContentValues value = new ContentValues(); - value.put(SubscriptionManager.GROUP_UUID, groupUUID.toString()); - value.put(SubscriptionManager.GROUP_OWNER, callingPackage); - int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, - value, getSelectionForSubIdList(subIdList), null); - - if (DBG) logdl("createSubscriptionGroup update DB result: " + result); - - refreshCachedActiveSubscriptionInfoList(); - - notifySubscriptionInfoChanged(); - - MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUUID); - - return groupUUID; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private String getOwnerPackageOfSubGroup(ParcelUuid groupUuid) { - if (groupUuid == null) return null; - - List infoList = getSubInfo(SubscriptionManager.GROUP_UUID - + "=\'" + groupUuid.toString() + "\'", null); - - return ArrayUtils.isEmpty(infoList) ? null : infoList.get(0).getGroupOwner(); - } - - /** - * @param groupUuid a UUID assigned to the subscription group. - * @param callingPackage the package making the IPC. - * @return if callingPackage has carrier privilege on sublist. - * - */ - public boolean canPackageManageGroup(ParcelUuid groupUuid, String callingPackage) { - if (groupUuid == null) { - throw new IllegalArgumentException("Invalid groupUuid"); - } - - if (TextUtils.isEmpty(callingPackage)) { - throw new IllegalArgumentException("Empty callingPackage"); - } - - List infoList; - - // Getting all subscriptions in the group. - long identity = Binder.clearCallingIdentity(); - try { - infoList = getSubInfo(SubscriptionManager.GROUP_UUID - + "=\'" + groupUuid.toString() + "\'", null); - } finally { - Binder.restoreCallingIdentity(identity); - } - - // If the group does not exist, then by default the UUID is up for grabs so no need to - // restrict management of a group (that someone may be attempting to create). - if (ArrayUtils.isEmpty(infoList)) { - return true; - } - - // If the calling package is the group owner, skip carrier permission check and return - // true as it was done before. - if (callingPackage.equals(infoList.get(0).getGroupOwner())) return true; - - // Check carrier privilege for all subscriptions in the group. - int[] subIdArray = infoList.stream().mapToInt(info -> info.getSubscriptionId()) - .toArray(); - return (checkCarrierPrivilegeOnSubList(subIdArray, callingPackage)); - } - - private int updateGroupOwner(ParcelUuid groupUuid, String groupOwner) { - // If the existing group owner is different from current caller, make caller the new - // owner of all subscriptions in group. - // This is for use-case of: - // 1) Both package1 and package2 has permission (MODIFY_PHONE_STATE or carrier - // privilege permission) of all related subscriptions. - // 2) Package 1 created a group. - // 3) Package 2 wants to add a subscription into it. - // Step 3 should be granted as all operations are permission based. Which means as - // long as the package passes the permission check, it can modify the subscription - // and the group. And package 2 becomes the new group owner as it's the last to pass - // permission checks on all members. - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.GROUP_OWNER, groupOwner); - return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, - value, SubscriptionManager.GROUP_UUID + "=\"" + groupUuid + "\"", null); - } - - @Override - public void addSubscriptionsIntoGroup(int[] subIdList, ParcelUuid groupUuid, - String callingPackage) { - if (subIdList == null || subIdList.length == 0) { - throw new IllegalArgumentException("Invalid subId list"); - } - - if (groupUuid == null || groupUuid.equals(INVALID_GROUP_UUID)) { - throw new IllegalArgumentException("Invalid groupUuid"); - } - - // Makes sure calling package matches caller UID. - mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - // If it doesn't have modify phone state permission, or carrier privilege permission, - // a SecurityException will be thrown. - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - != PERMISSION_GRANTED && !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage) - && canPackageManageGroup(groupUuid, callingPackage))) { - throw new SecurityException("Requires MODIFY_PHONE_STATE or carrier privilege" - + " permissions on subscriptions and the group."); - } - - long identity = Binder.clearCallingIdentity(); - - try { - if (DBG) { - logdl("addSubscriptionsIntoGroup sub list " - + Arrays.toString(subIdList) + " into group " + groupUuid); - } - - ContentValues value = new ContentValues(); - value.put(SubscriptionManager.GROUP_UUID, groupUuid.toString()); - int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, - value, getSelectionForSubIdList(subIdList), null); - - if (DBG) logdl("addSubscriptionsIntoGroup update DB result: " + result); - - if (result > 0) { - updateGroupOwner(groupUuid, callingPackage); - refreshCachedActiveSubscriptionInfoList(); - notifySubscriptionInfoChanged(); - MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUuid); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Remove a list of subscriptions from their subscription group. - * See {@link SubscriptionManager#createSubscriptionGroup(List)} for more details. - * - * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} - * permission or had carrier privilege permission on the subscriptions: - * {@link TelephonyManager#hasCarrierPrivileges()} or - * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)} - * - * @throws SecurityException if the caller doesn't meet the requirements - * outlined above. - * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong - * the specified group. - * - * @param subIdList list of subId that need removing from their groups. - * - */ - public void removeSubscriptionsFromGroup(int[] subIdList, ParcelUuid groupUuid, - String callingPackage) { - if (subIdList == null || subIdList.length == 0) { - return; - } - - // Makes sure calling package matches caller UID. - mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); - // If it doesn't have modify phone state permission, or carrier privilege permission, - // a SecurityException will be thrown. If it's due to invalid parameter or internal state, - // it will return null. - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - != PERMISSION_GRANTED && !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage) - && canPackageManageGroup(groupUuid, callingPackage))) { - throw new SecurityException("removeSubscriptionsFromGroup needs MODIFY_PHONE_STATE or" - + " carrier privilege permission on all specified subscriptions"); - } - - long identity = Binder.clearCallingIdentity(); - - try { - List subInfoList = getSubInfo(getSelectionForSubIdList(subIdList), - null); - for (SubscriptionInfo info : subInfoList) { - if (!groupUuid.equals(info.getGroupUuid())) { - throw new IllegalArgumentException("Subscription " + info.getSubscriptionId() - + " doesn't belong to group " + groupUuid); - } - } - ContentValues value = new ContentValues(); - value.put(SubscriptionManager.GROUP_UUID, (String) null); - value.put(SubscriptionManager.GROUP_OWNER, (String) null); - int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, - value, getSelectionForSubIdList(subIdList), null); - - if (DBG) logdl("removeSubscriptionsFromGroup update DB result: " + result); - - if (result > 0) { - updateGroupOwner(groupUuid, callingPackage); - refreshCachedActiveSubscriptionInfoList(); - notifySubscriptionInfoChanged(); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Helper function to check if the caller has carrier privilege permissions on a list of subId. - * The check can either be processed against access rules on currently active SIM cards, or - * the access rules we keep in our database for currently inactive eSIMs. - * - * @throws IllegalArgumentException if the some subId is invalid or doesn't exist. - * - * @return true if checking passes on all subId, false otherwise. - */ - private boolean checkCarrierPrivilegeOnSubList(int[] subIdList, String callingPackage) { - // Check carrier privilege permission on active subscriptions first. - // If it fails, they could be inactive. So keep them in a HashSet and later check - // access rules in our database. - Set checkSubList = new HashSet<>(); - for (int subId : subIdList) { - if (isActiveSubId(subId)) { - if (!mTelephonyManager.hasCarrierPrivileges(subId)) { - return false; - } - } else { - checkSubList.add(subId); - } - } - - if (checkSubList.isEmpty()) { - return true; - } - - long identity = Binder.clearCallingIdentity(); - - try { - // Check access rules for each sub info. - SubscriptionManager subscriptionManager = (SubscriptionManager) - mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - List subInfoList = getSubInfo( - getSelectionForSubIdList(subIdList), null); - - // Didn't find all the subscriptions specified in subIdList. - if (subInfoList == null || subInfoList.size() != subIdList.length) { - throw new IllegalArgumentException("Invalid subInfoList."); - } - - for (SubscriptionInfo subInfo : subInfoList) { - if (checkSubList.contains(subInfo.getSubscriptionId())) { - if (subInfo.isEmbedded() && subscriptionManager.canManageSubscription( - subInfo, callingPackage)) { - checkSubList.remove(subInfo.getSubscriptionId()); - } else { - return false; - } - } - } - - return checkSubList.isEmpty(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Helper function to create selection argument of a list of subId. - * The result should be: "in (subId1, subId2, ...)". - */ - public static String getSelectionForSubIdList(int[] subId) { - StringBuilder selection = new StringBuilder(); - selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID); - selection.append(" IN ("); - for (int i = 0; i < subId.length - 1; i++) { - selection.append(subId[i] + ", "); - } - selection.append(subId[subId.length - 1]); - selection.append(")"); - - return selection.toString(); - } - - /** - * Helper function to create selection argument of a list of subId. - * The result should be: "in (iccId1, iccId2, ...)". - */ - private String getSelectionForIccIdList(String[] iccIds) { - StringBuilder selection = new StringBuilder(); - selection.append(SubscriptionManager.ICC_ID); - selection.append(" IN ("); - for (int i = 0; i < iccIds.length - 1; i++) { - selection.append("'" + iccIds[i] + "', "); - } - selection.append("'" + iccIds[iccIds.length - 1] + "'"); - selection.append(")"); - - return selection.toString(); - } - - /** - * Get subscriptionInfo list of subscriptions that are in the same group of given subId. - * See {@link #createSubscriptionGroup(int[], String)} for more details. - * - * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE} - * or carrier privilege permission on the subscription. - * {@link TelephonyManager#hasCarrierPrivileges(int)} - * - *

Starting with API level 33, the caller needs READ_PHONE_STATE and access to device - * identifiers to get the list of subscriptions associated with a group UUID. - * This method can be invoked if one of the following requirements is met: - *

    - *
  • If the app has carrier privilege permission. - * {@link TelephonyManager#hasCarrierPrivileges()} - *
  • If the app has {@link android.Manifest.permission#READ_PHONE_STATE} and - * access to device identifiers. - *
- * - * @throws SecurityException if the caller doesn't meet the requirements - * outlined above. - * - * @param groupUuid of which list of subInfo will be returned. - * @return list of subscriptionInfo that belong to the same group, including the given - * subscription itself. It will return an empty list if no subscription belongs to the group. - * - */ - @Override - public List getSubscriptionsInGroup(ParcelUuid groupUuid, - String callingPackage, String callingFeatureId) { - long identity = Binder.clearCallingIdentity(); - List subInfoList; - - try { - // need to bypass removing identifier check because that will remove the subList without - // group id. - subInfoList = getAllSubInfoList(mContext.getOpPackageName(), - mContext.getAttributionTag(), true); - if (groupUuid == null || subInfoList == null || subInfoList.isEmpty()) { - return new ArrayList<>(); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - - // If the calling app neither has carrier privileges nor READ_PHONE_STATE and access to - // device identifiers, it will return an empty list. - if (CompatChanges.isChangeEnabled( - REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID, Binder.getCallingUid())) { - try { - if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext, - callingPackage, callingFeatureId, "getSubscriptionsInGroup")) { - EventLog.writeEvent(0x534e4554, "213902861", Binder.getCallingUid()); - return new ArrayList<>(); - } - } catch (SecurityException e) { - EventLog.writeEvent(0x534e4554, "213902861", Binder.getCallingUid()); - return new ArrayList<>(); - } - } - return subInfoList.stream().filter(info -> { - if (!groupUuid.equals(info.getGroupUuid())) return false; - int subId = info.getSubscriptionId(); - return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, - callingPackage, callingFeatureId, "getSubscriptionsInGroup") - || info.canManageSubscription(mContext, callingPackage); - }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo, - callingPackage, callingFeatureId, "getSubscriptionsInGroup")) - .collect(Collectors.toList()); - - } - - /** - * Check if the passed in phoneId has a sub that belongs to the same group as the sub - * corresponding to the passed in iccid. - * @param phoneId phone id to check - * @param iccid ICCID to check - * @return true if sub/group is the same, false otherwise - */ - public boolean checkPhoneIdAndIccIdMatch(int phoneId, String iccid) { - int subId = getSubId(phoneId); - if (!SubscriptionManager.isUsableSubIdValue(subId)) return false; - ParcelUuid groupUuid = getGroupUuid(subId); - List subInfoList; - if (groupUuid != null) { - subInfoList = getSubInfo(SubscriptionManager.GROUP_UUID - + "=\'" + groupUuid.toString() + "\'", null); - } else { - subInfoList = getSubInfo(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID - + "=" + subId, null); - } - return subInfoList != null && subInfoList.stream().anyMatch( - subInfo -> IccUtils.stripTrailingFs(subInfo.getIccId()).equals( - IccUtils.stripTrailingFs(iccid))); - } - - public ParcelUuid getGroupUuid(int subId) { - ParcelUuid groupUuid; - List subInfo = getSubInfo(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID - + "=" + subId, null); - if (subInfo == null || subInfo.size() == 0) { - groupUuid = null; - } else { - groupUuid = subInfo.get(0).getGroupUuid(); - } - - return groupUuid; - } - - - /** - * Enable/Disable a subscription - * @param enable true if enabling, false if disabling - * @param subId the unique SubInfoRecord index in database - * - * @return true if success, false if fails or the further action is - * needed hence it's redirected to Euicc. - */ - public boolean setSubscriptionEnabled(boolean enable, int subId) { - enforceModifyPhoneState("setSubscriptionEnabled"); - - final long identity = Binder.clearCallingIdentity(); - try { - logd("setSubscriptionEnabled" + (enable ? " enable " : " disable ") - + " subId " + subId); - - // Error checking. - if (!SubscriptionManager.isUsableSubscriptionId(subId)) { - throw new IllegalArgumentException( - "setSubscriptionEnabled not usable subId " + subId); - } - - // Nothing to do if it's already active or inactive. - if (enable == isActiveSubscriptionId(subId)) return true; - - SubscriptionInfo info = SubscriptionController.getInstance() - .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag()) - .stream() - .filter(subInfo -> subInfo.getSubscriptionId() == subId) - .findFirst() - .get(); - - if (info == null) { - logd("setSubscriptionEnabled subId " + subId + " doesn't exist."); - return false; - } - - // TODO: make sure after slot mapping, we enable the uicc applications for the - // subscription we are enabling. - if (info.isEmbedded()) { - return enableEmbeddedSubscription(info, enable); - } else { - return enablePhysicalSubscription(info, enable); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private boolean enableEmbeddedSubscription(SubscriptionInfo info, boolean enable) { - // We need to send intents to Euicc for operations: - - // 1) In single SIM mode, turning on a eSIM subscription while pSIM is the active slot. - // Euicc will ask user to switch to DSDS if supported or to confirm SIM slot - // switching. - // 2) In DSDS mode, turning on / off an eSIM profile. Euicc can ask user whether - // to turn on DSDS, or whether to switch from current active eSIM profile to it, or - // to simply show a progress dialog. - // 3) In future, similar operations on triple SIM devices. - enableSubscriptionOverEuiccManager(info.getSubscriptionId(), enable, - SubscriptionManager.INVALID_SIM_SLOT_INDEX); - // returning false to indicate state is not changed. If changed, a subscriptionInfo - // change will be filed separately. - return false; - - // TODO: uncomment or clean up if we decide whether to support standalone CBRS for Q. - // subId = enable ? subId : SubscriptionManager.INVALID_SUBSCRIPTION_ID; - // updateEnabledSubscriptionGlobalSetting(subId, physicalSlotIndex); - } - - private boolean enablePhysicalSubscription(SubscriptionInfo info, boolean enable) { - if (info == null || !SubscriptionManager.isValidSubscriptionId(info.getSubscriptionId())) { - return false; - } - - int subId = info.getSubscriptionId(); - - UiccSlotInfo slotInfo = null; - int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - UiccSlotInfo[] slotsInfo = mTelephonyManager.getUiccSlotsInfo(); - if (slotsInfo == null) return false; - for (int i = 0; i < slotsInfo.length; i++) { - UiccSlotInfo curSlotInfo = slotsInfo[i]; - if (curSlotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT) { - if (TextUtils.equals(IccUtils.stripTrailingFs(curSlotInfo.getCardId()), - IccUtils.stripTrailingFs(info.getCardString()))) { - slotInfo = curSlotInfo; - physicalSlotIndex = i; - break; - } - } - } - - // Can't find the existing SIM. - if (slotInfo == null) { - loge("Can't find the existing SIM."); - return false; - } - - // this for physical slot which has only one port - if (enable && !slotInfo.getPorts().stream().findFirst().get().isActive()) { - // We need to send intents to Euicc if we are turning on an inactive slot. - // Euicc will decide whether to ask user to switch to DSDS, or change SIM - // slot mapping. - EuiccManager euiccManager = - (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE); - if (euiccManager != null && euiccManager.isEnabled()) { - enableSubscriptionOverEuiccManager(subId, enable, physicalSlotIndex); - } else { - // Enable / disable uicc applications. - if (!info.areUiccApplicationsEnabled()) setUiccApplicationsEnabled(enable, subId); - // If euiccManager is not enabled, we try to switch to DSDS if possible, - // or switch slot if not. - if (mTelephonyManager.isMultiSimSupported() == MULTISIM_ALLOWED) { - PhoneConfigurationManager.getInstance().switchMultiSimConfig( - mTelephonyManager.getSupportedModemCount()); - } else { - List slotMapping = new ArrayList<>(); - // As this is single sim mode, set port index to 0 and logical slot index is 0 - slotMapping.add(new UiccSlotMapping(TelephonyManager.DEFAULT_PORT_INDEX, - physicalSlotIndex, 0)); - UiccController.getInstance().switchSlots(slotMapping, null); - } - } - return true; - } else { - // Enable / disable uicc applications. - setUiccApplicationsEnabled(enable, subId); - return true; - } - } - - private void enableSubscriptionOverEuiccManager(int subId, boolean enable, - int physicalSlotIndex) { - logdl("enableSubscriptionOverEuiccManager" + (enable ? " enable " : " disable ") - + "subId " + subId + " on slotIndex " + physicalSlotIndex); - Intent intent = new Intent(EuiccManager.ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(EuiccManager.EXTRA_SUBSCRIPTION_ID, subId); - intent.putExtra(EuiccManager.EXTRA_ENABLE_SUBSCRIPTION, enable); - if (physicalSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { - intent.putExtra(EuiccManager.EXTRA_PHYSICAL_SLOT_ID, physicalSlotIndex); - } - mContext.startActivity(intent); - } - - private void updateEnabledSubscriptionGlobalSetting(int subId, int physicalSlotIndex) { - // Write the value which subscription is enabled into global setting. - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + physicalSlotIndex, subId); - } - - private void updateModemStackEnabledGlobalSetting(boolean enabled, int physicalSlotIndex) { - // Write the whether a modem stack is disabled into global setting. - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT - + physicalSlotIndex, enabled ? 1 : 0); - } - - private int getPhysicalSlotIndexFromLogicalSlotIndex(int logicalSlotIndex) { - int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo(); - for (int i = 0; i < slotInfos.length; i++) { - for (UiccPortInfo portInfo : slotInfos[i].getPorts()) { - if (portInfo.getLogicalSlotIndex() == logicalSlotIndex) { - physicalSlotIndex = i; - break; - } - } - } - - return physicalSlotIndex; - } - - @Override - public boolean isSubscriptionEnabled(int subId) { - // TODO: b/123314365 support multi-eSIM and removable eSIM. - enforceReadPrivilegedPhoneState("isSubscriptionEnabled"); - - long identity = Binder.clearCallingIdentity(); - try { - // Error checking. - if (!SubscriptionManager.isUsableSubscriptionId(subId)) { - throw new IllegalArgumentException( - "isSubscriptionEnabled not usable subId " + subId); - } - - List infoList = getSubInfo( - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null); - if (infoList == null || infoList.isEmpty()) { - // Subscription doesn't exist. - return false; - } - - boolean isEmbedded = infoList.get(0).isEmbedded(); - - if (isEmbedded) { - return isActiveSubId(subId); - } else { - // For pSIM, we also need to check if modem is disabled or not. - return isActiveSubId(subId) && PhoneConfigurationManager.getInstance() - .getPhoneStatus(PhoneFactory.getPhone(getPhoneId(subId))); - } - - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public int getEnabledSubscriptionId(int logicalSlotIndex) { - // TODO: b/123314365 support multi-eSIM and removable eSIM. - enforceReadPrivilegedPhoneState("getEnabledSubscriptionId"); - - long identity = Binder.clearCallingIdentity(); - try { - if (!SubscriptionManager.isValidPhoneId(logicalSlotIndex)) { - throw new IllegalArgumentException( - "getEnabledSubscriptionId with invalid logicalSlotIndex " - + logicalSlotIndex); - } - - // Getting and validating the physicalSlotIndex. - int physicalSlotIndex = getPhysicalSlotIndexFromLogicalSlotIndex(logicalSlotIndex); - if (physicalSlotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { - return SubscriptionManager.INVALID_SUBSCRIPTION_ID; - } - - // if modem stack is disabled, return INVALID_SUBSCRIPTION_ID without reading - // Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT. - int modemStackEnabled = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT + physicalSlotIndex, 1); - if (modemStackEnabled != 1) { - return SubscriptionManager.INVALID_SUBSCRIPTION_ID; - } - - int subId; - try { - subId = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + physicalSlotIndex); - } catch (Settings.SettingNotFoundException e) { - // Value never set. Return whether it's currently active. - subId = getSubId(logicalSlotIndex); - } - - return subId; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList. - * They are doing similar things except operating on different cache. - * - * NOTE: the cacheSubList passed in is a *copy* of mCacheActiveSubInfoList or - * mCacheOpportunisticSubInfoList, so mSubInfoListLock is not required to access it. Also, this - * method may modify cacheSubList depending on the permissions the caller has. - */ - private List getSubscriptionInfoListFromCacheHelper( - String callingPackage, String callingFeatureId, List cacheSubList) { - boolean canReadPhoneState = false; - boolean canReadIdentifiers = false; - boolean canReadPhoneNumber = false; - try { - canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext, - SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(), - Binder.getCallingUid(), callingPackage, callingFeatureId, - "getSubscriptionInfoList"); - // If the calling package has the READ_PHONE_STATE permission then check if the caller - // also has access to subscriber identifiers and the phone number to ensure that the ICC - // ID and any other unique identifiers are removed if the caller should not have access. - if (canReadPhoneState) { - canReadIdentifiers = hasSubscriberIdentifierAccess( - SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, - callingFeatureId, "getSubscriptionInfoList", false); - canReadPhoneNumber = hasPhoneNumberAccess( - SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, - callingFeatureId, "getSubscriptionInfoList"); - } - } catch (SecurityException e) { - // If a SecurityException is thrown during the READ_PHONE_STATE check then the only way - // to access a subscription is to have carrier privileges for its subId; an app with - // carrier privileges for a subscription is also granted access to all identifiers so - // the identifier and phone number access checks are not required. - } - - if (canReadIdentifiers && canReadPhoneNumber) { - return cacheSubList; - } - // Filter the list to only include subscriptions which the caller can manage. - for (int subIndex = cacheSubList.size() - 1; subIndex >= 0; subIndex--) { - SubscriptionInfo subscriptionInfo = cacheSubList.get(subIndex); - - int subId = subscriptionInfo.getSubscriptionId(); - boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId( - mContext, subId); - // If the caller has carrier privileges then they are granted access to all - // identifiers for their subscription. - if (hasCarrierPrivileges) continue; - - cacheSubList.remove(subIndex); - if (canReadPhoneState) { - // The caller does not have carrier privileges for this subId, filter the - // identifiers in the subscription based on the results of the initial - // permission checks. - cacheSubList.add(subIndex, conditionallyRemoveIdentifiers( - subscriptionInfo, canReadIdentifiers, canReadPhoneNumber)); - } - } - return cacheSubList; - } - - /** - * Conditionally removes identifiers from the provided {@code subInfo} if the {@code - * callingPackage} does not meet the access requirements for identifiers and returns the - * potentially modified object.. - * - *

If the caller does not meet the access requirements for identifiers a clone of the - * provided SubscriptionInfo is created and modified to avoid altering SubscriptionInfo objects - * in a cache. - */ - private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, - String callingPackage, String callingFeatureId, String message) { - SubscriptionInfo result = subInfo; - int subId = subInfo.getSubscriptionId(); - boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage, - callingFeatureId, message, true); - boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage, callingFeatureId, - message); - return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess, hasPhoneNumberAccess); - } - - /** - * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling - * package {@code hasIdentifierAccess} and {@code hasPhoneNumberAccess} and returns the - * potentially modified object. - * - *

If the caller specifies the package does not have identifier or phone number access - * a clone of the provided SubscriptionInfo is created and modified to avoid altering - * SubscriptionInfo objects in a cache. - */ - private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, - boolean hasIdentifierAccess, boolean hasPhoneNumberAccess) { - if (hasIdentifierAccess && hasPhoneNumberAccess) { - return subInfo; - } - SubscriptionInfo.Builder result = new SubscriptionInfo.Builder(subInfo); - if (!hasIdentifierAccess) { - result.setIccId(null); - result.setCardString(null); - result.setGroupUuid(null); - } - if (!hasPhoneNumberAccess) { - result.setNumber(null); - } - return result.build(); - } - - private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) { - ArrayList subIdsList = mSlotIndexToSubIds.getCopy(slotIndex); - if (subIdsList == null) { - subIdsList = new ArrayList<>(); - mSlotIndexToSubIds.put(slotIndex, subIdsList); - } - - // add the given subId unless it already exists - if (subIdsList.contains(subId)) { - logdl("slotIndex, subId combo already exists in the map. Not adding it again."); - return false; - } - if (isSubscriptionForRemoteSim(subscriptionType)) { - // For Remote SIM subscriptions, a slot can have multiple subscriptions. - mSlotIndexToSubIds.addToSubIdList(slotIndex, subId); - } else { - // for all other types of subscriptions, a slot can have only one subscription at a time - mSlotIndexToSubIds.clearSubIdList(slotIndex); - mSlotIndexToSubIds.addToSubIdList(slotIndex, subId); - } - - - // Remove the slot from sSlotIndexToSubIds if it has the same sub id with the added slot - for (Entry> entry : mSlotIndexToSubIds.entrySet()) { - if (entry.getKey() != slotIndex && entry.getValue() != null - && entry.getValue().contains(subId)) { - logdl("addToSubIdList - remove " + entry.getKey()); - mSlotIndexToSubIds.remove(entry.getKey()); - } - } - - if (DBG) logdl("slotIndex, subId combo is added to the map."); - return true; - } - - private boolean isSubscriptionForRemoteSim(int subscriptionType) { - return subscriptionType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM; - } - - /** - * This is only for testing - * @hide - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public Map> getSlotIndexToSubIdsMap() { - return mSlotIndexToSubIds.getMap(); - } - - private void notifyOpportunisticSubscriptionInfoChanged() { - TelephonyRegistryManager trm = - (TelephonyRegistryManager) - mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); - if (DBG) logd("notifyOpptSubscriptionInfoChanged:"); - trm.notifyOpportunisticSubscriptionInfoChanged(); - } - - private void refreshCachedOpportunisticSubscriptionInfoList() { - synchronized (mSubInfoListLock) { - List subList = getSubInfo( - SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND (" - + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR " - + SubscriptionManager.IS_EMBEDDED + "=1)", null); - List oldOpptCachedList = mCacheOpportunisticSubInfoList; - - if (subList != null) { - subList.sort(SUBSCRIPTION_INFO_COMPARATOR); - } else { - subList = new ArrayList<>(); - } - - mCacheOpportunisticSubInfoList = subList; - - for (int i = 0; i < mCacheOpportunisticSubInfoList.size(); i++) { - SubscriptionInfo info = mCacheOpportunisticSubInfoList.get(i); - if (shouldDisableSubGroup(info.getGroupUuid())) { - SubscriptionInfo.Builder builder = new SubscriptionInfo.Builder(info); - builder.setGroupDisabled(true); - mCacheOpportunisticSubInfoList.set(i, builder.build()); - } - } - - if (DBG_CACHE) { - if (!mCacheOpportunisticSubInfoList.isEmpty()) { - for (SubscriptionInfo si : mCacheOpportunisticSubInfoList) { - logd("[refreshCachedOpportunisticSubscriptionInfoList] Setting Cached " - + "info=" + si); - } - } else { - logdl("[refreshCachedOpportunisticSubscriptionInfoList]- no info return"); - } - } - - if (!oldOpptCachedList.equals(mCacheOpportunisticSubInfoList)) { - mOpptSubInfoListChangedDirtyBit.set(true); - } - } - } - - private boolean shouldDisableSubGroup(ParcelUuid groupUuid) { - if (groupUuid == null) return false; - - synchronized (mSubInfoListLock) { - for (SubscriptionInfo activeInfo : mCacheActiveSubInfoList) { - if (!activeInfo.isOpportunistic() && groupUuid.equals(activeInfo.getGroupUuid())) { - return false; - } - } - } - - return true; - } - - /** - * Set enabled mobile data policies. - * - * @param subId Subscription index - * @param policies Mobile data policies in string format. - * See {@link TelephonyManager.MobileDataPolicy} for details. - * @return {@code true} if settings changed, otherwise {@code false}. - */ - public boolean setEnabledMobileDataPolicies(int subId, @NonNull String policies) { - if (DBG) logd("[setEnabledMobileDataPolicies]+ policies:" + policies + " subId:" + subId); - - validateSubId(subId); - ContentValues value = new ContentValues(1); - value.put(SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES, policies); - - boolean result = updateDatabase(value, subId, true) > 0; - - if (result) { - // Refresh the Cache of Active Subscription Info List - refreshCachedActiveSubscriptionInfoList(); - notifySubscriptionInfoChanged(); - } - - return result; - } - - /** - * Get enabled mobile data policies. - * - * @param subId Subscription index - * @return Enabled mobile data policies joined by "," (ie. "1,2") or an empty string if no - * policies are enabled. - */ - @NonNull - public String getEnabledMobileDataPolicies(int subId) { - return TelephonyUtils.emptyIfNull(getSubscriptionProperty(subId, - SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES)); - } - - /** - * Get active data subscription id. - * - * @return Active data subscription id - * - * @hide - */ - @Override - public int getActiveDataSubscriptionId() { - final long token = Binder.clearCallingIdentity(); - - try { - PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance(); - if (phoneSwitcher != null) { - int activeDataSubId = phoneSwitcher.getActiveDataSubId(); - if (SubscriptionManager.isUsableSubscriptionId(activeDataSubId)) { - return activeDataSubId; - } - } - // If phone switcher isn't ready, or active data sub id is not available, use default - // sub id from settings. - return getDefaultDataSubId(); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Whether it's supported to disable / re-enable a subscription on a physical (non-euicc) SIM. - */ - @Override - public boolean canDisablePhysicalSubscription() { - enforceReadPrivilegedPhoneState("canToggleUiccApplicationsEnablement"); - - final long identity = Binder.clearCallingIdentity(); - try { - Phone phone = PhoneFactory.getDefaultPhone(); - return phone != null && phone.canDisablePhysicalSubscription(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /* - * Returns the phone number for the given {@code subId} and {@code source}, - * or an empty string if not available. - */ - @Override - public String getPhoneNumber(int subId, int source, - String callingPackage, String callingFeatureId) { - TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges( - mContext, subId, Binder.getCallingUid(), "getPhoneNumber", - READ_PHONE_NUMBERS, READ_PRIVILEGED_PHONE_STATE); - - final long identity = Binder.clearCallingIdentity(); - try { - String number = getPhoneNumber(subId, source); - return number == null ? "" : number; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /* - * Returns the phone number for the given {@code subId} or an empty string if not available. - * - *

Built up on getPhoneNumber(int subId, int source) this API picks the 1st available - * source based on a priority order. - */ - @Override - public String getPhoneNumberFromFirstAvailableSource(int subId, - String callingPackage, String callingFeatureId) { - TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges( - mContext, subId, Binder.getCallingUid(), "getPhoneNumberFromFirstAvailableSource", - READ_PHONE_NUMBERS, READ_PRIVILEGED_PHONE_STATE); - - final long identity = Binder.clearCallingIdentity(); - try { - String numberFromCarrier = getPhoneNumber( - subId, SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER); - if (!TextUtils.isEmpty(numberFromCarrier)) { - return numberFromCarrier; - } - String numberFromUicc = getPhoneNumber( - subId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC); - if (!TextUtils.isEmpty(numberFromUicc)) { - return numberFromUicc; - } - String numberFromIms = getPhoneNumber( - subId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS); - if (!TextUtils.isEmpty(numberFromIms)) { - return numberFromIms; - } - return ""; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - // Internal helper method for implementing getPhoneNumber() API. - @Nullable - private String getPhoneNumber(int subId, int source) { - if (source == SubscriptionManager.PHONE_NUMBER_SOURCE_UICC) { - Phone phone = PhoneFactory.getPhone(getPhoneId(subId)); - return phone != null ? phone.getLine1Number() : null; - } - if (source == SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER) { - return getSubscriptionProperty(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER); - } - if (source == SubscriptionManager.PHONE_NUMBER_SOURCE_IMS) { - return getSubscriptionProperty(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS); - } - throw new IllegalArgumentException("setPhoneNumber doesn't accept source " + source); - } - - /** - * Sets the phone number for the given {@code subId}. - * - *

The only accepted {@code source} is {@link - * SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER}. - */ - @Override - public void setPhoneNumber(int subId, int source, String number, - String callingPackage, String callingFeatureId) { - if (source != SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER) { - throw new IllegalArgumentException("setPhoneNumber doesn't accept source " + source); - } - if (!TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext, subId)) { - throw new SecurityException("setPhoneNumber for CARRIER needs carrier privilege"); - } - if (number == null) { - throw new NullPointerException("invalid number null"); - } - - final long identity = Binder.clearCallingIdentity(); - try { - setSubscriptionProperty(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER, number); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Set the Usage Setting for this subscription. - * - * @param usageSetting the cellular usage setting - * @param subId the unique SubscriptionInfo index in database - * @param callingPackage the package making the IPC - * @return the number of records updated - * - * @throws SecurityException if doesn't have required permission. - */ - @Override - public int setUsageSetting(@UsageSetting int usageSetting, int subId, String callingPackage) { - try { - TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege( - mContext, subId, callingPackage); - } catch (SecurityException e) { - enforceCarrierPrivilegeOnInactiveSub(subId, callingPackage, - "Caller requires permission on sub " + subId); - } - - if (usageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT - || usageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) { - throw new IllegalArgumentException("setUsageSetting: Invalid usage setting: " - + usageSetting); - } - - final long token = Binder.clearCallingIdentity(); - int ret; - try { - ret = setSubscriptionProperty(subId, SubscriptionManager.USAGE_SETTING, - String.valueOf(usageSetting)); - - // ret is the number of records updated in the DB, which should always be 1. - // TODO(b/205027930): move this check prior to the database mutation request - if (ret != 1) throw new IllegalArgumentException( - "Invalid SubscriptionId for setUsageSetting"); - } finally { - Binder.restoreCallingIdentity(token); - // FIXME(b/205726099) return void - } - return ret; - } - - /** - * Querying last used TP - MessageRef for particular subId from SIMInfo table. - * @return messageRef - */ - public int getMessageRef(int subId) { - try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI, - new String[]{SubscriptionManager.TP_MESSAGE_REF}, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=\"" + subId - + "\"", null, null)) { - try { - if (cursor != null && cursor.moveToFirst()) { - do { - return cursor.getInt(cursor.getColumnIndexOrThrow( - SubscriptionManager.TP_MESSAGE_REF)); - } while (cursor.moveToNext()); - } else { - if (DBG) logd("Valid row not present in db"); - } - } catch (Exception e) { - if (DBG) logd("Query failed " + e.getMessage()); - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - return -1; - } - - /** - * Update the TP - Message Reference value for every SMS Sent - * @param messageRef - * @param subId - */ - public void updateMessageRef(int subId, int messageRef) { - TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege( - mContext, subId, mContext.getOpPackageName()); - - if (mContext == null) { - logel("[updateMessageRef] mContext is null"); - return; - } - try { - if (SubscriptionManager.CONTENT_URI != null) { - ContentValues values = new ContentValues(1); - values.put(SubscriptionManager.TP_MESSAGE_REF, messageRef); - mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=\"" + subId - + "\"", null); - } else { - if (DBG) logd("TP - Message reference value not updated to DB"); - } - } finally { - if (DBG) logd("TP - Message reference updated to DB Successfully :" + messageRef); - } - } - - /** - * Set UserHandle for this subscription - * - * @param userHandle the userHandle associated with the subscription - * Pass {@code null} user handle to clear the association - * @param subId the unique SubscriptionInfo index in database - * @return the number of records updated. - * - * @throws SecurityException if doesn't have required permission. - * @throws IllegalArgumentException if subId is invalid. - */ - @Override - public int setSubscriptionUserHandle(@Nullable UserHandle userHandle, int subId) { - enforceManageSubscriptionUserAssociation("setSubscriptionUserHandle"); - - if (userHandle == null) { - userHandle = UserHandle.of(UserHandle.USER_NULL); - } - - long token = Binder.clearCallingIdentity(); - try { - int ret = setSubscriptionProperty(subId, SubscriptionManager.USER_HANDLE, - String.valueOf(userHandle.getIdentifier())); - // ret is the number of records updated in the DB - if (ret != 0) { - notifySubscriptionInfoChanged(); - } else { - throw new IllegalArgumentException("[setSubscriptionUserHandle]: Invalid subId: " - + subId); - } - return ret; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Get UserHandle of this subscription. - * - * @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. - * - * @throws SecurityException if doesn't have required permission. - * @throws IllegalArgumentException if subId is invalid. - */ - @Override - @Nullable - public UserHandle getSubscriptionUserHandle(int subId) { - enforceManageSubscriptionUserAssociation("getSubscriptionUserHandle"); - - if (!mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enable_get_subscription_user_handle)) { - return null; - } - - long token = Binder.clearCallingIdentity(); - try { - String userHandleStr = getSubscriptionProperty(subId, SubscriptionManager.USER_HANDLE); - if (userHandleStr == null) { - throw new IllegalArgumentException("[getSubscriptionUserHandle]: Invalid subId: " - + subId); - } - UserHandle userHandle = UserHandle.of(Integer.parseInt(userHandleStr)); - if (userHandle.getIdentifier() == UserHandle.USER_NULL) { - return null; - } - return userHandle; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Check if subscription and user are associated with each other. - * - * @param subscriptionId the subId of the subscription - * @param userHandle user handle of the user - * @return {@code true} if subscription is associated with user - * {code true} if there are no subscriptions on device - * else {@code false} if subscription is not associated with user. - * - * @throws SecurityException if the caller doesn't have permissions required. - * @throws IllegalStateException if subscription service is not available. - * - */ - @Override - public boolean isSubscriptionAssociatedWithUser(int subscriptionId, - @NonNull UserHandle userHandle) { - enforceManageSubscriptionUserAssociation("isSubscriptionAssociatedWithUser"); - - long token = Binder.clearCallingIdentity(); - try { - // Return true if there are no subscriptions on the device. - List subInfoList = getAllSubInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - if (subInfoList == null || subInfoList.isEmpty()) { - return true; - } - - // Get list of subscriptions associated with this user. - List associatedSubscriptionsList = - getSubscriptionInfoListAssociatedWithUser(userHandle); - if (associatedSubscriptionsList.isEmpty()) { - return false; - } - - // Return true if required subscription is present in associated subscriptions list. - for (SubscriptionInfo subInfo: associatedSubscriptionsList) { - if (subInfo.getSubscriptionId() == subscriptionId){ - return true; - } - } - return false; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * Get list of subscriptions associated with user. - * - * If user handle is associated with some subscriptions, return subscriptionsAssociatedWithUser - * else return all the subscriptions which are not associated with any user. - * - * @param userHandle user handle of the user - * @return list of subscriptionInfo associated with the user. - * - * @throws SecurityException if the caller doesn't have permissions required. - * @throws IllegalStateException if subscription service is not available. - * - */ - @Override - public @NonNull List getSubscriptionInfoListAssociatedWithUser( - @NonNull UserHandle userHandle) { - enforceManageSubscriptionUserAssociation("getActiveSubscriptionInfoListAssociatedWithUser"); - - long token = Binder.clearCallingIdentity(); - try { - List subInfoList = getAllSubInfoList( - mContext.getOpPackageName(), mContext.getAttributionTag()); - if (subInfoList == null || subInfoList.isEmpty()) { - return new ArrayList<>(); - } - - List subscriptionsAssociatedWithUser = new ArrayList<>(); - List subscriptionsWithNoAssociation = new ArrayList<>(); - for (SubscriptionInfo subInfo : subInfoList) { - int subId = subInfo.getSubscriptionId(); - UserHandle subIdUserHandle = getSubscriptionUserHandle(subId); - if (userHandle.equals(subIdUserHandle)) { - // Store subscriptions whose user handle matches with required user handle. - subscriptionsAssociatedWithUser.add(subInfo); - } else if (subIdUserHandle == null) { - // Store subscriptions whose user handle is set to null. - subscriptionsWithNoAssociation.add(subInfo); - } - } - - return subscriptionsAssociatedWithUser.isEmpty() ? - subscriptionsWithNoAssociation : subscriptionsAssociatedWithUser; - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * @return {@code true} if using {@link SubscriptionManagerService} instead of - * {@link SubscriptionController}. - */ - //TODO: Removed before U AOSP public release. - @Override - public boolean isSubscriptionManagerServiceEnabled() { - return false; - } - - /** - * Called during setup wizard restore flow to attempt to restore the backed up sim-specific - * configs to device for all existing SIMs in the subscription database {@link SimInfo}. - * Internally, it will store the backup data in an internal file. This file will persist on - * device for device's lifetime and will be used later on when a SIM is inserted to restore that - * specific SIM's settings. End result is subscription database is modified to match any backed - * up configs for the appropriate inserted SIMs. - * - *

- * The {@link Uri} {@link SubscriptionManager#SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is - * notified if any {@link SimInfo} entry is updated as the result of this method call. - * - * @param data with the sim specific configs to be backed up. - */ - @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) - @Override - public void restoreAllSimSpecificSettingsFromBackup(@NonNull byte[] data) { - enforceModifyPhoneState("restoreAllSimSpecificSettingsFromBackup"); - - long token = Binder.clearCallingIdentity(); - try { - Bundle bundle = new Bundle(); - bundle.putByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA, data); - mContext.getContentResolver().call( - SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, - SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME, - null, bundle); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** - * @hide - */ - private void setGlobalSetting(String name, int value) { - Settings.Global.putInt(mContext.getContentResolver(), name, value); - if (TextUtils.equals(name, Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION)) { - invalidateDefaultDataSubIdCaches(); - invalidateActiveDataSubIdCaches(); - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } else if (TextUtils.equals(name, Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION)) { - invalidateDefaultSubIdCaches(); - invalidateSlotIndexCaches(); - } else if (TextUtils.equals(name, Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION)) { - invalidateDefaultSmsSubIdCaches(); - } - } - - private static void invalidateDefaultSubIdCaches() { - SubscriptionManager.invalidateDefaultSubIdCaches(); - } - - private static void invalidateDefaultDataSubIdCaches() { - SubscriptionManager.invalidateDefaultDataSubIdCaches(); - } - - private static void invalidateDefaultSmsSubIdCaches() { - SubscriptionManager.invalidateDefaultSmsSubIdCaches(); - } - - private static void invalidateActiveDataSubIdCaches() { - SubscriptionManager.invalidateActiveDataSubIdCaches(); - } - - private static void invalidateSlotIndexCaches() { - SubscriptionManager.invalidateSlotIndexCaches(); - } -} diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java deleted file mode 100644 index ea49d7303fa6af2edbb29068226ac7ae49ccb4e0..0000000000000000000000000000000000000000 --- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java +++ /dev/null @@ -1,1358 +0,0 @@ -/* -* Copyright (C) 2014 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; - -import android.Manifest; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.os.AsyncResult; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.ParcelUuid; -import android.os.PersistableBundle; -import android.os.UserHandle; -import android.preference.PreferenceManager; -import android.service.carrier.CarrierIdentifier; -import android.service.euicc.EuiccProfileInfo; -import android.service.euicc.EuiccService; -import android.service.euicc.GetEuiccProfileInfoListResult; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.SubscriptionManager.UsageSetting; -import android.telephony.TelephonyManager; -import android.telephony.UiccAccessRule; -import android.telephony.euicc.EuiccManager; -import android.text.TextUtils; -import android.util.Pair; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.euicc.EuiccController; -import com.android.internal.telephony.metrics.TelephonyMetrics; -import com.android.internal.telephony.uicc.IccRecords; -import com.android.internal.telephony.uicc.IccUtils; -import com.android.internal.telephony.uicc.UiccCard; -import com.android.internal.telephony.uicc.UiccController; -import com.android.internal.telephony.uicc.UiccPort; -import com.android.internal.telephony.uicc.UiccSlot; -import com.android.telephony.Rlog; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArraySet; - -/** - *@hide - */ -public class SubscriptionInfoUpdater extends Handler { - private static final String LOG_TAG = "SubscriptionInfoUpdater"; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static final int SUPPORTED_MODEM_COUNT = TelephonyManager.getDefault() - .getSupportedModemCount(); - - private static final boolean DBG = true; - - private static final int EVENT_INVALID = -1; - private static final int EVENT_GET_NETWORK_SELECTION_MODE_DONE = 2; - private static final int EVENT_SIM_LOADED = 3; - private static final int EVENT_SIM_ABSENT = 4; - private static final int EVENT_SIM_LOCKED = 5; - private static final int EVENT_SIM_IO_ERROR = 6; - private static final int EVENT_SIM_UNKNOWN = 7; - private static final int EVENT_SIM_RESTRICTED = 8; - private static final int EVENT_SIM_NOT_READY = 9; - private static final int EVENT_SIM_READY = 10; - private static final int EVENT_SIM_IMSI = 11; - private static final int EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS = 12; - private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 13; - private static final int EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED = 14; - - private static final String ICCID_STRING_FOR_NO_SIM = ""; - - private static final ParcelUuid REMOVE_GROUP_UUID = - ParcelUuid.fromString(CarrierConfigManager.REMOVE_GROUP_UUID_STRING); - - // Key used to read/write the current IMSI. Updated on SIM_STATE_CHANGED - LOADED. - public static final String CURR_SUBID = "curr_subid"; - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static Context sContext = null; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - - protected static String[] sIccId = new String[SUPPORTED_MODEM_COUNT]; - protected SubscriptionController mSubscriptionController = null; - private static String[] sInactiveIccIds = new String[SUPPORTED_MODEM_COUNT]; - private static int[] sSimCardState = new int[SUPPORTED_MODEM_COUNT]; - private static int[] sSimApplicationState = new int[SUPPORTED_MODEM_COUNT]; - private static boolean sIsSubInfoInitialized = false; - private SubscriptionManager mSubscriptionManager = null; - private EuiccManager mEuiccManager; - private Handler mBackgroundHandler; - - // The current foreground user ID. - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private int mCurrentlyActiveUserId; - private CarrierServiceBindHelper mCarrierServiceBindHelper; - - private volatile boolean shouldRetryUpdateEmbeddedSubscriptions = false; - private final CopyOnWriteArraySet retryUpdateEmbeddedSubscriptionCards = - new CopyOnWriteArraySet<>(); - private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { - // The LPA may not have been ready before user unlock, and so previous attempts - // to refresh the list of embedded subscriptions may have failed. This retries - // the refresh operation after user unlock. - if (shouldRetryUpdateEmbeddedSubscriptions) { - logd("Retrying refresh embedded subscriptions after user unlock."); - for (int cardId : retryUpdateEmbeddedSubscriptionCards){ - requestEmbeddedSubscriptionInfoListRefresh(cardId, null); - } - retryUpdateEmbeddedSubscriptionCards.clear(); - sContext.unregisterReceiver(mUserUnlockedReceiver); - } - } - } - }; - - /** - * Runnable with a boolean parameter. This is used in - * updateEmbeddedSubscriptions(List cardIds, @Nullable UpdateEmbeddedSubsCallback). - */ - protected interface UpdateEmbeddedSubsCallback { - /** - * Callback of the Runnable. - * @param hasChanges Whether there is any subscription info change. If yes, we need to - * notify the listeners. - */ - void run(boolean hasChanges); - } - - @VisibleForTesting - public SubscriptionInfoUpdater(Looper looper, Context context, SubscriptionController sc) { - logd("Constructor invoked"); - mBackgroundHandler = new Handler(looper); - - sContext = context; - mSubscriptionController = sc; - mSubscriptionManager = SubscriptionManager.from(sContext); - mEuiccManager = (EuiccManager) sContext.getSystemService(Context.EUICC_SERVICE); - - mCarrierServiceBindHelper = new CarrierServiceBindHelper(sContext); - - sContext.registerReceiver( - mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED)); - - initializeCarrierApps(); - - PhoneConfigurationManager.registerForMultiSimConfigChange( - this, EVENT_MULTI_SIM_CONFIG_CHANGED, null); - } - - private void initializeCarrierApps() { - // Initialize carrier apps: - // -Now (on system startup) - // -Whenever new carrier privilege rules might change (new SIM is loaded) - // -Whenever we switch to a new user - mCurrentlyActiveUserId = 0; - sContext.registerReceiverForAllUsers(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Remove this line after testing - if (Intent.ACTION_USER_FOREGROUND.equals(intent.getAction())) { - UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); - // If couldn't get current user ID, guess it's 0. - mCurrentlyActiveUserId = userHandle != null ? userHandle.getIdentifier() : 0; - CarrierAppUtils.disableCarrierAppsUntilPrivileged(sContext.getOpPackageName(), - TelephonyManager.getDefault(), mCurrentlyActiveUserId, sContext); - } - } - }, new IntentFilter(Intent.ACTION_USER_FOREGROUND), null, null); - ActivityManager am = (ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE); - mCurrentlyActiveUserId = am.getCurrentUser(); - CarrierAppUtils.disableCarrierAppsUntilPrivileged(sContext.getOpPackageName(), - TelephonyManager.getDefault(), mCurrentlyActiveUserId, sContext); - } - - /** - * Update subscriptions when given a new ICC state. - */ - public void updateInternalIccState(String simStatus, String reason, int phoneId) { - logd("updateInternalIccState to simStatus " + simStatus + " reason " + reason - + " phoneId " + phoneId); - int message = internalIccStateToMessage(simStatus); - if (message != EVENT_INVALID) { - sendMessage(obtainMessage(message, phoneId, 0, reason)); - } - } - - /** - * Update subscriptions if needed when there's a change in inactive port. - * @param prevActivePhoneId is the corresponding phoneId of the port if port was previously - * active. It could be INVALID if it was already inactive. - * @param iccId iccId in that port, if any. - */ - public void updateInternalIccStateForInactivePort(int prevActivePhoneId, String iccId) { - sendMessage(obtainMessage(EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED, prevActivePhoneId, - 0, iccId)); - } - - private int internalIccStateToMessage(String simStatus) { - switch(simStatus) { - case IccCardConstants.INTENT_VALUE_ICC_ABSENT: return EVENT_SIM_ABSENT; - case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN: return EVENT_SIM_UNKNOWN; - case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR: return EVENT_SIM_IO_ERROR; - case IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED: return EVENT_SIM_RESTRICTED; - case IccCardConstants.INTENT_VALUE_ICC_NOT_READY: return EVENT_SIM_NOT_READY; - case IccCardConstants.INTENT_VALUE_ICC_LOCKED: return EVENT_SIM_LOCKED; - case IccCardConstants.INTENT_VALUE_ICC_LOADED: return EVENT_SIM_LOADED; - case IccCardConstants.INTENT_VALUE_ICC_READY: return EVENT_SIM_READY; - case IccCardConstants.INTENT_VALUE_ICC_IMSI: return EVENT_SIM_IMSI; - default: - logd("Ignoring simStatus: " + simStatus); - return EVENT_INVALID; - } - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected boolean isAllIccIdQueryDone() { - for (int i = 0; i < TelephonyManager.getDefault().getActiveModemCount(); i++) { - UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(i); - int slotId = UiccController.getInstance().getSlotIdFromPhoneId(i); - // When psim card is absent there is no port object even the port state is active. - // We should check the slot state for psim and port state for esim(MEP eUICC). - if (sIccId[i] == null || slot == null || !slot.isActive() - || (slot.isEuicc() && UiccController.getInstance().getUiccPort(i) == null)) { - if (sIccId[i] == null) { - logd("Wait for SIM " + i + " Iccid"); - } else { - logd(String.format("Wait for port corresponding to phone %d to be active, " - + "slotId is %d" + " , portIndex is %d", i, slotId, - slot.getPortIndexFromPhoneId(i))); - } - return false; - } - } - logd("All IccIds query complete"); - - return true; - } - - @Override - public void handleMessage(Message msg) { - List cardIds = new ArrayList<>(); - switch (msg.what) { - case EVENT_GET_NETWORK_SELECTION_MODE_DONE: { - AsyncResult ar = (AsyncResult)msg.obj; - Integer slotId = (Integer)ar.userObj; - if (ar.exception == null && ar.result != null) { - int[] modes = (int[])ar.result; - if (modes[0] == 1) { // Manual mode. - PhoneFactory.getPhone(slotId).setNetworkSelectionModeAutomatic(null); - } - } else { - logd("EVENT_GET_NETWORK_SELECTION_MODE_DONE: error getting network mode."); - } - break; - } - - case EVENT_SIM_LOADED: - handleSimLoaded(msg.arg1); - break; - - case EVENT_SIM_ABSENT: - handleSimAbsent(msg.arg1); - break; - - case EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED: - handleInactivePortIccStateChange(msg.arg1, (String) msg.obj); - break; - - case EVENT_SIM_LOCKED: - handleSimLocked(msg.arg1, (String) msg.obj); - break; - - case EVENT_SIM_UNKNOWN: - broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null); - broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN); - broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN); - updateSubscriptionCarrierId(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN); - updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN); - break; - - case EVENT_SIM_IO_ERROR: - handleSimError(msg.arg1); - break; - - case EVENT_SIM_RESTRICTED: - broadcastSimStateChanged(msg.arg1, - IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED, - IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED); - broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_CARD_RESTRICTED); - broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY); - updateSubscriptionCarrierId(msg.arg1, - IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED); - updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED); - break; - - case EVENT_SIM_READY: - handleSimReady(msg.arg1); - break; - - case EVENT_SIM_IMSI: - broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_IMSI, null); - break; - - case EVENT_SIM_NOT_READY: - // an eUICC with no active subscriptions never becomes ready, so we need to trigger - // the embedded subscriptions update here - cardIds.add(getCardIdFromPhoneId(msg.arg1)); - updateEmbeddedSubscriptions(cardIds, (hasChanges) -> { - if (hasChanges) { - mSubscriptionController.notifySubscriptionInfoChanged(); - } - }); - handleSimNotReady(msg.arg1); - break; - - case EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS: - cardIds.add(msg.arg1); - Runnable r = (Runnable) msg.obj; - updateEmbeddedSubscriptions(cardIds, (hasChanges) -> { - if (hasChanges) { - mSubscriptionController.notifySubscriptionInfoChanged(); - } - if (r != null) { - r.run(); - } - }); - break; - - case EVENT_MULTI_SIM_CONFIG_CHANGED: - onMultiSimConfigChanged(); - break; - - default: - logd("Unknown msg:" + msg.what); - } - } - - private void onMultiSimConfigChanged() { - int activeModemCount = ((TelephonyManager) sContext.getSystemService( - Context.TELEPHONY_SERVICE)).getActiveModemCount(); - // For inactive modems, reset its states. - for (int phoneId = activeModemCount; phoneId < SUPPORTED_MODEM_COUNT; phoneId++) { - sIccId[phoneId] = null; - sSimCardState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN; - sSimApplicationState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN; - } - } - - protected int getCardIdFromPhoneId(int phoneId) { - UiccController uiccController = UiccController.getInstance(); - UiccCard card = uiccController.getUiccCardForPhone(phoneId); - if (card != null) { - return uiccController.convertToPublicCardId(card.getCardId()); - } - return TelephonyManager.UNINITIALIZED_CARD_ID; - } - - void requestEmbeddedSubscriptionInfoListRefresh(int cardId, @Nullable Runnable callback) { - sendMessage(obtainMessage( - EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback)); - } - - protected void handleSimLocked(int phoneId, String reason) { - if (sIccId[phoneId] != null && sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) { - logd("SIM" + (phoneId + 1) + " hot plug in"); - sIccId[phoneId] = null; - } - - IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard(); - if (iccCard == null) { - logd("handleSimLocked: IccCard null"); - return; - } - IccRecords records = iccCard.getIccRecords(); - if (records == null) { - logd("handleSimLocked: IccRecords null"); - return; - } - if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) { - logd("handleSimLocked: IccID null"); - return; - } - sIccId[phoneId] = IccUtils.stripTrailingFs(records.getFullIccId()); - - updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */); - - broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED, reason); - broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT); - broadcastSimApplicationStateChanged(phoneId, getSimStateFromLockedReason(reason)); - updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED); - updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED); - } - - private static int getSimStateFromLockedReason(String lockedReason) { - switch (lockedReason) { - case IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN: - return TelephonyManager.SIM_STATE_PIN_REQUIRED; - case IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK: - return TelephonyManager.SIM_STATE_PUK_REQUIRED; - case IccCardConstants.INTENT_VALUE_LOCKED_NETWORK: - return TelephonyManager.SIM_STATE_NETWORK_LOCKED; - case IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED: - return TelephonyManager.SIM_STATE_PERM_DISABLED; - default: - Rlog.e(LOG_TAG, "Unexpected SIM locked reason " + lockedReason); - return TelephonyManager.SIM_STATE_UNKNOWN; - } - } - - protected void handleSimReady(int phoneId) { - List cardIds = new ArrayList<>(); - logd("handleSimReady: phoneId: " + phoneId); - - if (sIccId[phoneId] != null && sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) { - logd(" SIM" + (phoneId + 1) + " hot plug in"); - sIccId[phoneId] = null; - } - - // ICCID is not available in IccRecords by the time SIM Ready event received - // hence get ICCID from UiccPort. - UiccPort port = UiccController.getInstance().getUiccPort(phoneId); - String iccId = (port == null) ? null : IccUtils.stripTrailingFs(port.getIccId()); - - if (!TextUtils.isEmpty(iccId)) { - sIccId[phoneId] = iccId; - updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */); - } - - cardIds.add(getCardIdFromPhoneId(phoneId)); - updateEmbeddedSubscriptions(cardIds, (hasChanges) -> { - if (hasChanges) { - mSubscriptionController.notifySubscriptionInfoChanged(); - } - }); - broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_READY, null); - broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT); - broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY); - } - - protected void handleSimNotReady(int phoneId) { - logd("handleSimNotReady: phoneId: " + phoneId); - boolean isFinalState = false; - - IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard(); - boolean uiccAppsDisabled = areUiccAppsDisabledOnCard(phoneId); - if (iccCard.isEmptyProfile() || uiccAppsDisabled) { - if (uiccAppsDisabled) { - UiccPort port = UiccController.getInstance().getUiccPort(phoneId); - String iccId = (port == null) ? null : port.getIccId(); - sInactiveIccIds[phoneId] = IccUtils.stripTrailingFs(iccId); - } - isFinalState = true; - // ICC_NOT_READY is a terminal state for - // 1) It's an empty profile as there's no uicc applications. Or - // 2) Its uicc applications are set to be disabled. - // At this phase, the subscription list is accessible. Treating NOT_READY - // as equivalent to ABSENT, once the rest of the system can handle it. - sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM; - updateSubscriptionInfoByIccId(phoneId, false /* updateEmbeddedSubs */); - } else { - sIccId[phoneId] = null; - } - - broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY, - null); - broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT); - broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY); - if (isFinalState) { - updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY); - } - } - - private boolean areUiccAppsDisabledOnCard(int phoneId) { - // When uicc apps are disabled(supported in IRadio 1.5), we will still get IccId from - // cardStatus (since IRadio 1.2). Amd upon cardStatus change we'll receive another - // handleSimNotReady so this will be evaluated again. - UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId); - if (slot == null) return false; - UiccPort port = UiccController.getInstance().getUiccPort(phoneId); - String iccId = (port == null) ? null : port.getIccId(); - if (iccId == null) { - return false; - } - SubscriptionInfo info = - mSubscriptionController.getSubInfoForIccId( - IccUtils.stripTrailingFs(iccId)); - return info != null && !info.areUiccApplicationsEnabled(); - } - - protected void handleSimLoaded(int phoneId) { - logd("handleSimLoaded: phoneId: " + phoneId); - - // The SIM should be loaded at this state, but it is possible in cases such as SIM being - // removed or a refresh RESET that the IccRecords could be null. The right behavior is to - // not broadcast the SIM loaded. - IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard(); - if (iccCard == null) { // Possibly a race condition. - logd("handleSimLoaded: IccCard null"); - return; - } - IccRecords records = iccCard.getIccRecords(); - if (records == null) { // Possibly a race condition. - logd("handleSimLoaded: IccRecords null"); - return; - } - if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) { - logd("handleSimLoaded: IccID null"); - return; - } - - // Call updateSubscriptionInfoByIccId() only if was not done earlier from SIM READY event - if (sIccId[phoneId] == null) { - sIccId[phoneId] = IccUtils.stripTrailingFs(records.getFullIccId()); - - updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */); - } - - List subscriptionInfos = - mSubscriptionController.getSubInfoUsingSlotIndexPrivileged(phoneId); - if (subscriptionInfos == null || subscriptionInfos.isEmpty()) { - loge("empty subinfo for phoneId: " + phoneId + "could not update ContentResolver"); - } else { - for (SubscriptionInfo sub : subscriptionInfos) { - int subId = sub.getSubscriptionId(); - TelephonyManager tm = (TelephonyManager) - sContext.getSystemService(Context.TELEPHONY_SERVICE); - String operator = tm.getSimOperatorNumeric(subId); - - if (!TextUtils.isEmpty(operator)) { - if (subId == mSubscriptionController.getDefaultSubId()) { - MccTable.updateMccMncConfiguration(sContext, operator); - } - mSubscriptionController.setMccMnc(operator, subId); - } else { - logd("EVENT_RECORDS_LOADED Operator name is null"); - } - - String iso = tm.getSimCountryIsoForPhone(phoneId); - - if (!TextUtils.isEmpty(iso)) { - mSubscriptionController.setCountryIso(iso, subId); - } else { - logd("EVENT_RECORDS_LOADED sim country iso is null"); - } - - String msisdn = tm.getLine1Number(subId); - if (msisdn != null) { - mSubscriptionController.setDisplayNumber(msisdn, subId); - } - - String imsi = tm.createForSubscriptionId(subId).getSubscriberId(); - if (imsi != null) { - mSubscriptionController.setImsi(imsi, subId); - } - - String[] ehplmns = records.getEhplmns(); - String[] hplmns = records.getPlmnsFromHplmnActRecord(); - if (ehplmns != null || hplmns != null) { - mSubscriptionController.setAssociatedPlmns(ehplmns, hplmns, subId); - } - - /* Update preferred network type and network selection mode on SIM change. - * Storing last subId in SharedPreference for now to detect SIM change. - */ - SharedPreferences sp = - PreferenceManager.getDefaultSharedPreferences(sContext); - int storedSubId = sp.getInt(CURR_SUBID + phoneId, -1); - - if (storedSubId != subId) { - // Only support automatic selection mode on SIM change. - PhoneFactory.getPhone(phoneId).getNetworkSelectionMode( - obtainMessage(EVENT_GET_NETWORK_SELECTION_MODE_DONE, - new Integer(phoneId))); - // Update stored subId - SharedPreferences.Editor editor = sp.edit(); - editor.putInt(CURR_SUBID + phoneId, subId); - editor.apply(); - } - } - } - - /** - * The sim loading sequence will be - * 1. OnSubscriptionsChangedListener is called through updateSubscriptionInfoByIccId() - * above. - * 2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED - * /ACTION_SIM_APPLICATION_STATE_CHANGED - * 3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED - * 4. restore sim-specific settings - * 5. ACTION_CARRIER_CONFIG_CHANGED - */ - broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null); - broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT); - broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_LOADED); - updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED); - /* Sim-specific settings restore depends on knowing both the mccmnc and the carrierId of the - sim which is why it must be done after #updateSubscriptionCarrierId(). It is done before - carrier config update to avoid any race conditions with user settings that depend on - carrier config*/ - restoreSimSpecificSettingsForPhone(phoneId); - updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED); - } - - /** - * Calculate the usage setting based on the carrier request. - * - * @param currentUsageSetting the current setting in the subscription DB - * @param preferredUsageSetting provided by the carrier config - * @return the calculated usage setting. - */ - @VisibleForTesting - @UsageSetting public int calculateUsageSetting( - @UsageSetting int currentUsageSetting, @UsageSetting int preferredUsageSetting) { - int defaultUsageSetting; - int[] supportedUsageSettings; - - // Load the resources to provide the device capability - try { - defaultUsageSetting = sContext.getResources().getInteger( - com.android.internal.R.integer.config_default_cellular_usage_setting); - supportedUsageSettings = sContext.getResources().getIntArray( - com.android.internal.R.array.config_supported_cellular_usage_settings); - // If usage settings are not supported, return the default setting, which is UNKNOWN. - if (supportedUsageSettings == null - || supportedUsageSettings.length < 1) return currentUsageSetting; - } catch (Resources.NotFoundException nfe) { - loge("Failed to load usage setting resources!"); - return currentUsageSetting; - } - - // If the current setting is invalid, including the first time the value is set, - // update it to default (this will trigger a change in the DB). - if (currentUsageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT - || currentUsageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) { - logd("Updating usage setting for current subscription"); - currentUsageSetting = SubscriptionManager.USAGE_SETTING_DEFAULT; - } - - // Range check the inputs, and on failure, make no changes - if (preferredUsageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT - || preferredUsageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) { - loge("Invalid usage setting!" + preferredUsageSetting); - return currentUsageSetting; - } - - // Default is always allowed - if (preferredUsageSetting == SubscriptionManager.USAGE_SETTING_DEFAULT) { - return preferredUsageSetting; - } - - // Forced setting must be explicitly supported - for (int i = 0; i < supportedUsageSettings.length; i++) { - if (preferredUsageSetting == supportedUsageSettings[i]) return preferredUsageSetting; - } - - // If the preferred setting is not possible, just keep the current setting. - return currentUsageSetting; - } - - private void restoreSimSpecificSettingsForPhone(int phoneId) { - sContext.getContentResolver().call( - SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, - SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME, - sIccId[phoneId], null); - } - - private void updateCarrierServices(int phoneId, String simState) { - if (!SubscriptionManager.isValidPhoneId(phoneId)) { - logd("Ignore updateCarrierServices request with invalid phoneId " + phoneId); - return; - } - CarrierConfigManager configManager = - (CarrierConfigManager) sContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - configManager.updateConfigForPhoneId(phoneId, simState); - mCarrierServiceBindHelper.updateForPhoneId(phoneId, simState); - } - - private void updateSubscriptionCarrierId(int phoneId, String simState) { - if (PhoneFactory.getPhone(phoneId) != null) { - PhoneFactory.getPhone(phoneId).resolveSubscriptionCarrierId(simState); - } - } - - /** - * PhoneId is the corresponding phoneId of the port if port was previously active. - * It could be INVALID if it was already inactive. - */ - private void handleInactivePortIccStateChange(int phoneId, String iccId) { - if (SubscriptionManager.isValidPhoneId(phoneId)) { - // If phoneId is valid, it means the physical slot was previously active in that - // phoneId. In this case, found the subId and set its phoneId to invalid. - if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) { - logd("Slot of SIM" + (phoneId + 1) + " becomes inactive"); - } - cleanSubscriptionInPhone(phoneId, false); - } - if (!TextUtils.isEmpty(iccId)) { - // If iccId is new, add a subscription record in the db. - String strippedIccId = IccUtils.stripTrailingFs(iccId); - if (mSubscriptionController.getSubInfoForIccId(strippedIccId) == null) { - mSubscriptionController.insertEmptySubInfoRecord( - strippedIccId, "CARD", SubscriptionManager.INVALID_PHONE_INDEX, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - } - } - } - - /** - * Clean subscription info when sim state becomes ABSENT. There are 2 scenarios for this: - * 1. SIM is actually removed - * 2. Slot becomes inactive, which results in SIM being treated as ABSENT, but SIM may not - * have been removed. - * @param phoneId phoneId for which the cleanup needs to be done - * @param isSimAbsent boolean to indicate if the SIM is actually ABSENT (case 1 above) - */ - private void cleanSubscriptionInPhone(int phoneId, boolean isSimAbsent) { - if (sInactiveIccIds[phoneId] != null || (isSimAbsent && sIccId[phoneId] != null - && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM))) { - // When a SIM is unplugged, mark uicc applications enabled. This is to make sure when - // user unplugs and re-inserts the SIM card, we re-enable it. - // In certain cases this can happen before sInactiveIccIds is updated, which is why we - // check for sIccId as well (in case of isSimAbsent). The scenario is: after SIM - // deactivate request is sent to RIL, SIM is removed before SIM state is updated to - // NOT_READY. We do not need to check if this exact scenario is hit, because marking - // uicc applications enabled when SIM is removed should be okay to do regardless. - logd("cleanSubscriptionInPhone: " + phoneId + ", inactive iccid " - + sInactiveIccIds[phoneId]); - if (sInactiveIccIds[phoneId] == null) { - logd("cleanSubscriptionInPhone: " + phoneId + ", isSimAbsent=" + isSimAbsent - + ", iccid=" + sIccId[phoneId]); - } - String iccId = sInactiveIccIds[phoneId] != null - ? sInactiveIccIds[phoneId] : sIccId[phoneId]; - ContentValues value = new ContentValues(); - value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, true); - if (isSimAbsent) { - // When sim is absent, set the port index to invalid port index -1; - value.put(SubscriptionManager.PORT_INDEX, TelephonyManager.INVALID_PORT_INDEX); - } - sContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, - SubscriptionManager.ICC_ID + "=\'" + iccId + "\'", null); - sInactiveIccIds[phoneId] = null; - } - sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM; - updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */); - } - - protected void handleSimAbsent(int phoneId) { - if (!SubscriptionManager.isValidPhoneId(phoneId)) { - logd("handleSimAbsent on invalid phoneId"); - return; - } - if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) { - logd("SIM" + (phoneId + 1) + " hot plug out"); - } - cleanSubscriptionInPhone(phoneId, true); - - broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT, null); - broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_ABSENT); - broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_UNKNOWN); - updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT); - updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT); - } - - protected void handleSimError(int phoneId) { - if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) { - logd("SIM" + (phoneId + 1) + " Error "); - } - sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM; - updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */); - broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR, - IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR); - broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_CARD_IO_ERROR); - broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY); - updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR); - updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR); - } - - protected synchronized void updateSubscriptionInfoByIccId(int phoneId, - boolean updateEmbeddedSubs) { - logd("updateSubscriptionInfoByIccId:+ Start - phoneId: " + phoneId); - if (!SubscriptionManager.isValidPhoneId(phoneId)) { - loge("[updateSubscriptionInfoByIccId]- invalid phoneId=" + phoneId); - return; - } - logd("updateSubscriptionInfoByIccId: removing subscription info record: phoneId " - + phoneId); - // Clear phoneId only when sim absent is not enough. It's possible to switch SIM profile - // within the same slot. Need to clear the slot index of the previous sub. Thus always clear - // for the changing slot first. - mSubscriptionController.clearSubInfoRecord(phoneId); - - // If SIM is not absent, insert new record or update existing record. - if (!ICCID_STRING_FOR_NO_SIM.equals(sIccId[phoneId]) && sIccId[phoneId] != null) { - logd("updateSubscriptionInfoByIccId: adding subscription info record: iccid: " - + sIccId[phoneId] + ", phoneId:" + phoneId); - mSubscriptionManager.addSubscriptionInfoRecord(sIccId[phoneId], phoneId); - } - - List subInfos = - mSubscriptionController.getSubInfoUsingSlotIndexPrivileged(phoneId); - if (subInfos != null) { - boolean changed = false; - for (int i = 0; i < subInfos.size(); i++) { - SubscriptionInfo temp = subInfos.get(i); - ContentValues value = new ContentValues(1); - - String msisdn = TelephonyManager.getDefault().getLine1Number( - temp.getSubscriptionId()); - - if (!TextUtils.equals(msisdn, temp.getNumber())) { - value.put(SubscriptionManager.NUMBER, msisdn); - sContext.getContentResolver().update(SubscriptionManager - .getUriForSubscriptionId(temp.getSubscriptionId()), value, null, null); - changed = true; - } - } - if (changed) { - // refresh Cached Active Subscription Info List - mSubscriptionController.refreshCachedActiveSubscriptionInfoList(); - } - } - - // TODO investigate if we can update for each slot separately. - if (isAllIccIdQueryDone()) { - // Ensure the modems are mapped correctly - if (mSubscriptionManager.isActiveSubId( - mSubscriptionManager.getDefaultDataSubscriptionId())) { - mSubscriptionManager.setDefaultDataSubId( - mSubscriptionManager.getDefaultDataSubscriptionId()); - } else { - logd("bypass reset default data sub if inactive"); - } - setSubInfoInitialized(); - } - - UiccController uiccController = UiccController.getInstance(); - UiccSlot[] uiccSlots = uiccController.getUiccSlots(); - if (uiccSlots != null && updateEmbeddedSubs) { - List cardIds = new ArrayList<>(); - for (UiccSlot uiccSlot : uiccSlots) { - if (uiccSlot != null && uiccSlot.getUiccCard() != null) { - int cardId = uiccController.convertToPublicCardId( - uiccSlot.getUiccCard().getCardId()); - cardIds.add(cardId); - } - } - updateEmbeddedSubscriptions(cardIds, (hasChanges) -> { - if (hasChanges) { - mSubscriptionController.notifySubscriptionInfoChanged(); - } - if (DBG) logd("updateSubscriptionInfoByIccId: SubscriptionInfo update complete"); - }); - } - - mSubscriptionController.notifySubscriptionInfoChanged(); - if (DBG) logd("updateSubscriptionInfoByIccId: SubscriptionInfo update complete"); - } - - private void setSubInfoInitialized() { - // Should only be triggered once. - if (!sIsSubInfoInitialized) { - if (DBG) logd("SubInfo Initialized"); - sIsSubInfoInitialized = true; - mSubscriptionController.notifySubInfoReady(); - } - MultiSimSettingController.getInstance().notifyAllSubscriptionLoaded(); - } - - /** - * Whether subscriptions of all SIMs are initialized. - */ - public static boolean isSubInfoInitialized() { - return sIsSubInfoInitialized; - } - - /** - * Updates the cached list of embedded subscription for the eUICC with the given list of card - * IDs {@code cardIds}. The step of reading the embedded subscription list from eUICC card is - * executed in background thread. The callback {@code callback} is executed after the cache is - * refreshed. The callback is executed in main thread. - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public void updateEmbeddedSubscriptions(List cardIds, - @Nullable UpdateEmbeddedSubsCallback callback) { - // Do nothing if eUICCs are disabled. (Previous entries may remain in the cache, but they - // are filtered out of list calls as long as EuiccManager.isEnabled returns false). - if (!mEuiccManager.isEnabled()) { - if (DBG) logd("updateEmbeddedSubscriptions: eUICC not enabled"); - callback.run(false /* hasChanges */); - return; - } - - mBackgroundHandler.post(() -> { - List> results = new ArrayList<>(); - for (int cardId : cardIds) { - GetEuiccProfileInfoListResult result = - EuiccController.get().blockingGetEuiccProfileInfoList(cardId); - if (DBG) logd("blockingGetEuiccProfileInfoList cardId " + cardId); - results.add(Pair.create(cardId, result)); - } - - // The runnable will be executed in the main thread. - this.post(() -> { - boolean hasChanges = false; - for (Pair cardIdAndResult : results) { - if (updateEmbeddedSubscriptionsCache(cardIdAndResult.first, - cardIdAndResult.second)) { - hasChanges = true; - } - } - // The latest state in the main thread may be changed when the callback is - // triggered. - if (callback != null) { - callback.run(hasChanges); - } - }); - }); - } - - /** - * Update the cached list of embedded subscription based on the passed in - * GetEuiccProfileInfoListResult {@code result}. - * - * @return true if changes may have been made. This is not a guarantee that changes were made, - * but notifications about subscription changes may be skipped if this returns false as an - * optimization to avoid spurious notifications. - */ - private boolean updateEmbeddedSubscriptionsCache(int cardId, - GetEuiccProfileInfoListResult result) { - if (DBG) logd("updateEmbeddedSubscriptionsCache"); - - if (result == null) { - if (DBG) logd("updateEmbeddedSubscriptionsCache: IPC to the eUICC controller failed"); - retryUpdateEmbeddedSubscriptionCards.add(cardId); - shouldRetryUpdateEmbeddedSubscriptions = true; - return false; - } - - // If the returned result is not RESULT_OK or the profile list is null, don't update cache. - // Otherwise, update the cache. - final EuiccProfileInfo[] embeddedProfiles; - List list = result.getProfiles(); - if (result.getResult() == EuiccService.RESULT_OK && list != null) { - embeddedProfiles = list.toArray(new EuiccProfileInfo[list.size()]); - if (DBG) { - logd("blockingGetEuiccProfileInfoList: got " + result.getProfiles().size() - + " profiles"); - } - } else { - if (DBG) { - logd("blockingGetEuiccProfileInfoList returns an error. " - + "Result code=" + result.getResult() - + ". Null profile list=" + (result.getProfiles() == null)); - } - return false; - } - - final boolean isRemovable = result.getIsRemovable(); - - final String[] embeddedIccids = new String[embeddedProfiles.length]; - for (int i = 0; i < embeddedProfiles.length; i++) { - embeddedIccids[i] = embeddedProfiles[i].getIccid(); - } - - if (DBG) logd("Get eUICC profile list of size " + embeddedProfiles.length); - - // Note that this only tracks whether we make any writes to the DB. It's possible this will - // be set to true for an update even when the row contents remain exactly unchanged from - // before, since we don't compare against the previous value. Since this is only intended to - // avoid some spurious broadcasts (particularly for users who don't use eSIM at all), this - // is fine. - boolean hasChanges = false; - - // Update or insert records for all embedded subscriptions (except non-removable ones if the - // current eUICC is non-removable, since we assume these are still accessible though not - // returned by the eUICC controller). - List existingSubscriptions = - mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate( - embeddedIccids, isRemovable); - ContentResolver contentResolver = sContext.getContentResolver(); - for (EuiccProfileInfo embeddedProfile : embeddedProfiles) { - int index = - findSubscriptionInfoForIccid(existingSubscriptions, embeddedProfile.getIccid()); - int prevCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; - int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID; - if (index < 0) { - // No existing entry for this ICCID; create an empty one. - mSubscriptionController.insertEmptySubInfoRecord( - embeddedProfile.getIccid(), SubscriptionManager.SIM_NOT_INSERTED); - } else { - nameSource = existingSubscriptions.get(index).getDisplayNameSource(); - prevCarrierId = existingSubscriptions.get(index).getCarrierId(); - existingSubscriptions.remove(index); - } - - if (DBG) { - logd("embeddedProfile " + embeddedProfile + " existing record " - + (index < 0 ? "not found" : "found")); - } - - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, 1); - List ruleList = embeddedProfile.getUiccAccessRules(); - boolean isRuleListEmpty = false; - if (ruleList == null || ruleList.size() == 0) { - isRuleListEmpty = true; - } - values.put(SubscriptionManager.ACCESS_RULES, - isRuleListEmpty ? null : UiccAccessRule.encodeRules( - ruleList.toArray(new UiccAccessRule[ruleList.size()]))); - values.put(SubscriptionManager.IS_REMOVABLE, isRemovable); - // override DISPLAY_NAME if the priority of existing nameSource is <= carrier - if (SubscriptionController.getNameSourcePriority(nameSource) - <= SubscriptionController.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_CARRIER)) { - values.put(SubscriptionManager.DISPLAY_NAME, embeddedProfile.getNickname()); - values.put(SubscriptionManager.NAME_SOURCE, - SubscriptionManager.NAME_SOURCE_CARRIER); - } - values.put(SubscriptionManager.PROFILE_CLASS, embeddedProfile.getProfileClass()); - values.put(SubscriptionManager.PORT_INDEX, - getEmbeddedProfilePortIndex(embeddedProfile.getIccid())); - CarrierIdentifier cid = embeddedProfile.getCarrierIdentifier(); - if (cid != null) { - // Due to the limited subscription information, carrier id identified here might - // not be accurate compared with CarrierResolver. Only update carrier id if there - // is no valid carrier id present. - if (prevCarrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { - values.put(SubscriptionManager.CARRIER_ID, - CarrierResolver.getCarrierIdFromIdentifier(sContext, cid)); - } - String mcc = cid.getMcc(); - String mnc = cid.getMnc(); - values.put(SubscriptionManager.MCC_STRING, mcc); - values.put(SubscriptionManager.MCC, mcc); - values.put(SubscriptionManager.MNC_STRING, mnc); - values.put(SubscriptionManager.MNC, mnc); - } - // If cardId = unsupported or unitialized, we have no reason to update DB. - // Additionally, if the device does not support cardId for default eUICC, the CARD_ID - // field should not contain the EID - UiccController uiccController = UiccController.getInstance(); - if (cardId >= 0 && uiccController.getCardIdForDefaultEuicc() - != TelephonyManager.UNSUPPORTED_CARD_ID) { - values.put(SubscriptionManager.CARD_ID, uiccController.convertToCardString(cardId)); - } - hasChanges = true; - contentResolver.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.ICC_ID + "='" + embeddedProfile.getIccid() + "'", null); - - // refresh Cached Active Subscription Info List - mSubscriptionController.refreshCachedActiveSubscriptionInfoList(); - } - - // Remove all remaining subscriptions which have embedded = true. We set embedded to false - // to ensure they are not returned in the list of embedded subscriptions (but keep them - // around in case the subscription is added back later, which is equivalent to a removable - // SIM being removed and reinserted). - if (!existingSubscriptions.isEmpty()) { - if (DBG) { - logd("Removing existing embedded subscriptions of size" - + existingSubscriptions.size()); - } - List iccidsToRemove = new ArrayList<>(); - for (int i = 0; i < existingSubscriptions.size(); i++) { - SubscriptionInfo info = existingSubscriptions.get(i); - if (info.isEmbedded()) { - if (DBG) logd("Removing embedded subscription of IccId " + info.getIccId()); - iccidsToRemove.add("'" + info.getIccId() + "'"); - } - } - String whereClause = SubscriptionManager.ICC_ID + " IN (" - + TextUtils.join(",", iccidsToRemove) + ")"; - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, 0); - hasChanges = true; - contentResolver.update(SubscriptionManager.CONTENT_URI, values, whereClause, null); - - // refresh Cached Active Subscription Info List - mSubscriptionController.refreshCachedActiveSubscriptionInfoList(); - } - - if (DBG) logd("updateEmbeddedSubscriptions done hasChanges=" + hasChanges); - return hasChanges; - } - - private int getEmbeddedProfilePortIndex(String iccId) { - UiccSlot[] slots = UiccController.getInstance().getUiccSlots(); - for (UiccSlot slot : slots) { - if (slot != null && slot.isEuicc() - && slot.getPortIndexFromIccId(iccId) != TelephonyManager.INVALID_PORT_INDEX) { - return slot.getPortIndexFromIccId(iccId); - } - } - return TelephonyManager.INVALID_PORT_INDEX; - } - /** - * Called by CarrierConfigLoader to update the subscription before sending a broadcast. - */ - public void updateSubscriptionByCarrierConfigAndNotifyComplete(int phoneId, - String configPackageName, PersistableBundle config, Message onComplete) { - post(() -> { - updateSubscriptionByCarrierConfig(phoneId, configPackageName, config); - onComplete.sendToTarget(); - }); - } - - private String getDefaultCarrierServicePackageName() { - CarrierConfigManager configManager = - (CarrierConfigManager) sContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - return configManager.getDefaultCarrierServicePackageName(); - } - - private boolean isCarrierServicePackage(int phoneId, String pkgName) { - if (pkgName.equals(getDefaultCarrierServicePackageName())) return false; - - String carrierPackageName = TelephonyManager.from(sContext) - .getCarrierServicePackageNameForLogicalSlot(phoneId); - if (DBG) logd("Carrier service package for subscription = " + carrierPackageName); - return pkgName.equals(carrierPackageName); - } - - /** - * Update the currently active Subscription based on information from CarrierConfig - */ - @VisibleForTesting - public void updateSubscriptionByCarrierConfig( - int phoneId, String configPackageName, PersistableBundle config) { - if (!SubscriptionManager.isValidPhoneId(phoneId) - || TextUtils.isEmpty(configPackageName) || config == null) { - if (DBG) { - logd("In updateSubscriptionByCarrierConfig(): phoneId=" + phoneId - + " configPackageName=" + configPackageName + " config=" - + ((config == null) ? "null" : config.hashCode())); - } - return; - } - - int currentSubId = mSubscriptionController.getSubId(phoneId); - if (!SubscriptionManager.isValidSubscriptionId(currentSubId) - || currentSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { - if (DBG) logd("No subscription is active for phone being updated"); - return; - } - - SubscriptionInfo currentSubInfo = mSubscriptionController.getSubscriptionInfo(currentSubId); - if (currentSubInfo == null) { - loge("Couldn't retrieve subscription info for current subscription"); - return; - } - - ContentValues cv = new ContentValues(); - ParcelUuid groupUuid = null; - - // carrier certificates are not subscription-specific, so we want to load them even if - // this current package is not a CarrierServicePackage - String[] certs = config.getStringArray( - CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY); - UiccAccessRule[] carrierConfigAccessRules = UiccAccessRule.decodeRulesFromCarrierConfig( - certs); - cv.put(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS, - UiccAccessRule.encodeRules(carrierConfigAccessRules)); - - if (!isCarrierServicePackage(phoneId, configPackageName)) { - loge("Cannot manage subId=" + currentSubId + ", carrierPackage=" + configPackageName); - } else { - boolean isOpportunistic = config.getBoolean( - CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, - currentSubInfo.isOpportunistic()); - if (currentSubInfo.isOpportunistic() != isOpportunistic) { - if (DBG) logd("Set SubId=" + currentSubId + " isOpportunistic=" + isOpportunistic); - cv.put(SubscriptionManager.IS_OPPORTUNISTIC, isOpportunistic ? "1" : "0"); - } - - String groupUuidString = - config.getString(CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, ""); - if (!TextUtils.isEmpty(groupUuidString)) { - try { - // Update via a UUID Structure to ensure consistent formatting - groupUuid = ParcelUuid.fromString(groupUuidString); - if (groupUuid.equals(REMOVE_GROUP_UUID) - && currentSubInfo.getGroupUuid() != null) { - cv.put(SubscriptionManager.GROUP_UUID, (String) null); - if (DBG) logd("Group Removed for" + currentSubId); - } else if (mSubscriptionController.canPackageManageGroup( - groupUuid, configPackageName)) { - cv.put(SubscriptionManager.GROUP_UUID, groupUuid.toString()); - cv.put(SubscriptionManager.GROUP_OWNER, configPackageName); - if (DBG) logd("Group Added for" + currentSubId); - } else { - loge("configPackageName " + configPackageName + " doesn't own grouUuid " - + groupUuid); - } - } catch (IllegalArgumentException e) { - loge("Invalid Group UUID=" + groupUuidString); - } - } - } - - final int preferredUsageSetting = - config.getInt( - CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT, - SubscriptionManager.USAGE_SETTING_UNKNOWN); - - @UsageSetting int newUsageSetting = calculateUsageSetting( - currentSubInfo.getUsageSetting(), - preferredUsageSetting); - - if (newUsageSetting != currentSubInfo.getUsageSetting()) { - cv.put(SubscriptionManager.USAGE_SETTING, newUsageSetting); - if (DBG) { - logd("UsageSetting changed," - + " oldSetting=" + currentSubInfo.getUsageSetting() - + " preferredSetting=" + preferredUsageSetting - + " newSetting=" + newUsageSetting); - } - } else { - if (DBG) { - logd("UsageSetting unchanged," - + " oldSetting=" + currentSubInfo.getUsageSetting() - + " preferredSetting=" + preferredUsageSetting - + " newSetting=" + newUsageSetting); - } - } - - if (cv.size() > 0 && sContext.getContentResolver().update(SubscriptionManager - .getUriForSubscriptionId(currentSubId), cv, null, null) > 0) { - mSubscriptionController.refreshCachedActiveSubscriptionInfoList(); - mSubscriptionController.notifySubscriptionInfoChanged(); - MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUuid); - } - } - - private static int findSubscriptionInfoForIccid(List list, String iccid) { - for (int i = 0; i < list.size(); i++) { - if (TextUtils.equals(iccid, list.get(i).getIccId())) { - return i; - } - } - return -1; - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected void broadcastSimStateChanged(int phoneId, String state, String reason) { - // Note: This intent is way deprecated and is only being kept around because there's no - // graceful way to deprecate a sticky broadcast that has a lot of listeners. - // DO NOT add any new extras to this broadcast -- it is not protected by any permissions. - Intent i = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); - i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - i.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone"); - i.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, state); - i.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason); - SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId); - logd("Broadcasting intent ACTION_SIM_STATE_CHANGED " + state + " reason " + reason + - " for phone: " + phoneId); - IntentBroadcaster.getInstance().broadcastStickyIntent(sContext, i, phoneId); - } - - protected void broadcastSimCardStateChanged(int phoneId, int state) { - if (state != sSimCardState[phoneId]) { - sSimCardState[phoneId] = state; - Intent i = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED); - i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state); - SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId); - // TODO(b/130664115) we manually populate this intent with the slotId. In the future we - // should do a review of whether to make this public - UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId); - int slotId = UiccController.getInstance().getSlotIdFromPhoneId(phoneId); - i.putExtra(PhoneConstants.SLOT_KEY, slotId); - if (slot != null) { - i.putExtra(PhoneConstants.PORT_KEY, slot.getPortIndexFromPhoneId(phoneId)); - } - logd("Broadcasting intent ACTION_SIM_CARD_STATE_CHANGED " - + TelephonyManager.simStateToString(state) + " for phone: " + phoneId - + " slot: " + slotId + " port: " + slot.getPortIndexFromPhoneId(phoneId)); - sContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE); - TelephonyMetrics.getInstance().updateSimState(phoneId, state); - } - } - - protected void broadcastSimApplicationStateChanged(int phoneId, int state) { - // Broadcast if the state has changed, except if old state was UNKNOWN and new is NOT_READY, - // because that's the initial state and a broadcast should be sent only on a transition - // after SIM is PRESENT. The only exception is eSIM boot profile, where NOT_READY is the - // terminal state. - boolean isUnknownToNotReady = - (sSimApplicationState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN - && state == TelephonyManager.SIM_STATE_NOT_READY); - IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard(); - boolean emptyProfile = iccCard != null && iccCard.isEmptyProfile(); - if (state != sSimApplicationState[phoneId] && (!isUnknownToNotReady || emptyProfile)) { - sSimApplicationState[phoneId] = state; - Intent i = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED); - i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state); - SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId); - // TODO(b/130664115) we populate this intent with the actual slotId. In the future we - // should do a review of whether to make this public - UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId); - int slotId = UiccController.getInstance().getSlotIdFromPhoneId(phoneId); - i.putExtra(PhoneConstants.SLOT_KEY, slotId); - if (slot != null) { - i.putExtra(PhoneConstants.PORT_KEY, slot.getPortIndexFromPhoneId(phoneId)); - } - logd("Broadcasting intent ACTION_SIM_APPLICATION_STATE_CHANGED " - + TelephonyManager.simStateToString(state) + " for phone: " + phoneId - + " slot: " + slotId + "port: " + slot.getPortIndexFromPhoneId(phoneId)); - sContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE); - TelephonyMetrics.getInstance().updateSimState(phoneId, state); - } - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static void logd(String message) { - Rlog.d(LOG_TAG, message); - } - - private static void loge(String message) { - Rlog.e(LOG_TAG, message); - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("SubscriptionInfoUpdater:"); - mCarrierServiceBindHelper.dump(fd, pw, args); - } -} diff --git a/src/java/com/android/internal/telephony/TelephonyAdminReceiver.java b/src/java/com/android/internal/telephony/TelephonyAdminReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..994405ba9aed35245c5c2e5dfc65baef36662b87 --- /dev/null +++ b/src/java/com/android/internal/telephony/TelephonyAdminReceiver.java @@ -0,0 +1,100 @@ +/* + * 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; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserManager; +import android.telephony.Rlog; + +/** + * A BroadcastReceiver for device administration events. + */ +public class TelephonyAdminReceiver extends BroadcastReceiver { + private static final String TAG = "TelephonyAdminReceiver"; + private final Phone mPhone; + // We keep track of the last value to avoid updating when unrelated user restrictions change + private boolean mDisallowCellular2gRestriction = false; + private final Context mContext; + private UserManager mUserManager; + + public TelephonyAdminReceiver(Context context, Phone phone) { + mContext = context; + mPhone = phone; + mUserManager = null; + if (ensureUserManagerExists()) { + mDisallowCellular2gRestriction = mUserManager.hasUserRestriction( + UserManager.DISALLOW_CELLULAR_2G); + } + IntentFilter filter = new IntentFilter(); + filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); + context.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + Rlog.d(TAG, "Processing onReceive"); + if (context == null || intent == null) return; + if (!intent.getAction().equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) { + Rlog.d(TAG, "Ignoring unexpected action: " + intent.getAction()); + return; + } + if (!ensureUserManagerExists()) { + return; + } + boolean shouldDisallow2g = mUserManager.hasUserRestriction( + UserManager.DISALLOW_CELLULAR_2G); + + if (shouldDisallow2g != mDisallowCellular2gRestriction) { + Rlog.i(TAG, + "Updating allowed network types with new admin 2g restriction. no_cellular_2g: " + + shouldDisallow2g); + mDisallowCellular2gRestriction = shouldDisallow2g; + mPhone.sendSubscriptionSettings(false); + } else { + Rlog.i(TAG, "Skipping update of allowed network types. Restriction no_cellular_2g " + + "unchanged: " + mDisallowCellular2gRestriction); + } + } + + /** + * Returns the current state of the {@link UserManager#DISALLOW_CELLULAR_2G} user restriction. + */ + public boolean isCellular2gDisabled() { + return mDisallowCellular2gRestriction; + } + + /** + * Tries to resolve the user manager system service. Returns true if successful, false + * otherwise. + */ + private boolean ensureUserManagerExists() { + if (mUserManager == null) { + Rlog.d(TAG, "No user manager. Attempting to resolve one."); + mUserManager = mContext.getSystemService(UserManager.class); + } + if (mUserManager == null) { + Rlog.e(TAG, + "Could not get a user manager instance. All operations will be no-ops until " + + "one is resolved"); + return false; + } + return true; + } +} diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java index 08c02e2ea97e55de53d49e728c28151160038f3e..f7f24a27ecca769acbf3f60bfe03aba97508fd3e 100644 --- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java +++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java @@ -41,6 +41,7 @@ import com.android.internal.telephony.data.LinkBandwidthEstimator; import com.android.internal.telephony.data.PhoneSwitcher; import com.android.internal.telephony.emergency.EmergencyNumberTracker; import com.android.internal.telephony.imsphone.ImsExternalCallTracker; +import com.android.internal.telephony.imsphone.ImsNrSaModeHandler; import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.imsphone.ImsPhoneCallTracker; import com.android.internal.telephony.nitz.NitzStateMachineImpl; @@ -385,6 +386,14 @@ public class TelephonyComponentFactory { return new ImsExternalCallTracker(imsPhone, imsPhone.getContext().getMainExecutor()); } + /** + * Create an ImsNrSaModeHandler. + */ + public ImsNrSaModeHandler makeImsNrSaModeHandler(ImsPhone imsPhone) { + + return new ImsNrSaModeHandler(imsPhone, imsPhone.getLooper()); + } + /** * Create an AppSmsManager for per-app SMS message. */ @@ -425,10 +434,6 @@ public class TelephonyComponentFactory { telephonyComponentFactory); } - public SubscriptionController initSubscriptionController(Context c) { - return SubscriptionController.init(c); - } - public PhoneSwitcher makePhoneSwitcher(int maxDataAttachModemCount, Context context, Looper looper) { return PhoneSwitcher.make(maxDataAttachModemCount, context, looper); @@ -441,9 +446,14 @@ public class TelephonyComponentFactory { return new DisplayInfoController(phone); } - public MultiSimSettingController initMultiSimSettingController(Context c, - SubscriptionController sc) { - return MultiSimSettingController.init(c, sc); + /** + * Initialize multi sim settings controller. + * + * @param c The context. + * @return The multi sim settings controller instance. + */ + public MultiSimSettingController initMultiSimSettingController(Context c) { + return MultiSimSettingController.init(c); } /** @@ -453,11 +463,6 @@ public class TelephonyComponentFactory { return new SignalStrengthController(phone); } - public SubscriptionInfoUpdater makeSubscriptionInfoUpdater(Looper looper, Context context, - SubscriptionController sc) { - return new SubscriptionInfoUpdater(looper, context, sc); - } - /** * Create a new LinkBandwidthEstimator. */ diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java index c81a4c3ce5ef0183722918029be6d5848e811243..b9e04c8ba3213b6329cdf74cdd6abaccff043b71 100644 --- a/src/java/com/android/internal/telephony/TelephonyTester.java +++ b/src/java/com/android/internal/telephony/TelephonyTester.java @@ -16,6 +16,7 @@ package com.android.internal.telephony; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -200,11 +201,7 @@ public class TelephonyTester { sendTestSuppServiceNotification(intent); } else if (action.equals(ACTION_TEST_SERVICE_STATE)) { log("handle test service state changed intent"); - // Trigger the service state update. The replacement will be done in - // overrideServiceState(). - mServiceStateTestIntent = intent; - mPhone.getServiceStateTracker().sendEmptyMessage( - ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED); + setServiceStateTestIntent(intent); } else if (action.equals(ACTION_TEST_IMS_E_CALL)) { log("handle test IMS ecall intent"); testImsECall(); @@ -388,6 +385,19 @@ public class TelephonyTester { } } + /** + * Set the service state test intent. + * + * @param intent The service state test intent. + */ + public void setServiceStateTestIntent(@NonNull Intent intent) { + mServiceStateTestIntent = intent; + // Trigger the service state update. The replacement will be done in + // overrideServiceState(). + mPhone.getServiceStateTracker().sendEmptyMessage( + ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED); + } + void overrideServiceState(ServiceState ss) { if (mServiceStateTestIntent == null || ss == null) return; if (mPhone.getPhoneId() != mServiceStateTestIntent.getIntExtra( @@ -401,13 +411,36 @@ public class TelephonyTester { } if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) { + int state = mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE, + ServiceState.STATE_OUT_OF_SERVICE); ss.setVoiceRegState(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_REG_STATE, ServiceState.STATE_OUT_OF_SERVICE)); + NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + NetworkRegistrationInfo.Builder builder = new NetworkRegistrationInfo.Builder(nri); + if (state == ServiceState.STATE_IN_SERVICE) { + builder.setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + } else { + builder.setRegistrationState( + NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING); + } + ss.addNetworkRegistrationInfo(builder.build()); log("Override voice service state with " + ss.getState()); } if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_REG_STATE)) { - ss.setDataRegState(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE, - ServiceState.STATE_OUT_OF_SERVICE)); + int state = mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE, + ServiceState.STATE_OUT_OF_SERVICE); + ss.setDataRegState(state); + NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + NetworkRegistrationInfo.Builder builder = new NetworkRegistrationInfo.Builder(nri); + if (state == ServiceState.STATE_IN_SERVICE) { + builder.setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + } else { + builder.setRegistrationState( + NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING); + } + ss.addNetworkRegistrationInfo(builder.build()); log("Override data service state with " + ss.getDataRegistrationState()); } if (mServiceStateTestIntent.hasExtra(EXTRA_OPERATOR)) { diff --git a/src/java/com/android/internal/telephony/UiccPhoneBookController.java b/src/java/com/android/internal/telephony/UiccPhoneBookController.java index 8b8457c2b8d111d2b20081a81314dd754e81df72..96fd20842d2f0c01c2b06a97cdc198c61f7c3a84 100644 --- a/src/java/com/android/internal/telephony/UiccPhoneBookController.java +++ b/src/java/com/android/internal/telephony/UiccPhoneBookController.java @@ -147,12 +147,7 @@ public class UiccPhoneBookController extends IIccPhoneBook.Stub { private IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(int subId) { - int phoneId; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId); - } else { - phoneId = SubscriptionController.getInstance().getPhoneId(subId); - } + int phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId); try { return PhoneFactory.getPhone(phoneId).getIccPhoneBookInterfaceManager(); } catch (NullPointerException e) { diff --git a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java index 0b1065160ecc23818be0127e99e200998aeee9f0..e3748110b41df8cfe4e1f79e5f05bb6a1d9ff660 100644 --- a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java +++ b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.os.UserHandle; import android.provider.VoicemailContract; import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; @@ -60,7 +61,7 @@ public class VisualVoicemailSmsFilter { /** * Convert the subId to a {@link PhoneAccountHandle} */ - PhoneAccountHandle fromSubId(int subId); + PhoneAccountHandle fromSubId(int subId, Context context); } private static final String TAG = "VvmSmsFilter"; @@ -77,7 +78,7 @@ public class VisualVoicemailSmsFilter { new PhoneAccountHandleConverter() { @Override - public PhoneAccountHandle fromSubId(int subId) { + public PhoneAccountHandle fromSubId(int subId, Context context) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { return null; } @@ -85,6 +86,15 @@ public class VisualVoicemailSmsFilter { if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { return null; } + SubscriptionManager subscriptionManager = + (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + UserHandle userHandle = subscriptionManager.getSubscriptionUserHandle(subId); + if (userHandle != null) { + return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, + Integer.toString(PhoneFactory.getPhone(phoneId).getSubId()), + userHandle); + } return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT, Integer.toString(PhoneFactory.getPhone(phoneId).getSubId())); } @@ -138,7 +148,8 @@ public class VisualVoicemailSmsFilter { return false; } - PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleConverter.fromSubId(subId); + PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleConverter.fromSubId(subId, + context); if (phoneAccountHandle == null) { Log.e(TAG, "Unable to convert subId " + subId + " to PhoneAccountHandle"); diff --git a/src/java/com/android/internal/telephony/VoiceIndication.java b/src/java/com/android/internal/telephony/VoiceIndication.java index 984d2a0aff2be88ab952419bf083717b4d052a07..9720bb76aba501e97c02ba5c5afe5848324f28ea 100644 --- a/src/java/com/android/internal/telephony/VoiceIndication.java +++ b/src/java/com/android/internal/telephony/VoiceIndication.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE; + import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CALL_RING; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_CALL_WAITING; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_INFO_REC; @@ -63,7 +65,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { */ public void callRing(int indicationType, boolean isGsm, android.hardware.radio.voice.CdmaSignalInfoRecord record) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); char[] response = null; @@ -90,7 +92,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param indicationType Type of radio indication */ public void callStateChanged(int indicationType) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED); @@ -104,7 +106,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { */ public void cdmaCallWaiting(int indicationType, android.hardware.radio.voice.CdmaCallWaiting callWaitingRecord) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); // TODO: create a CdmaCallWaitingNotification constructor that takes in these fields to make // sure no fields are missing @@ -134,7 +136,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { */ public void cdmaInfoRec(int indicationType, android.hardware.radio.voice.CdmaInformationRecord[] records) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); for (int i = 0; i < records.length; i++) { android.hardware.radio.voice.CdmaInformationRecord record = records[i]; @@ -233,7 +235,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param status CDMA OTA provision status */ public void cdmaOtaProvisionStatus(int indicationType, int status) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); int[] response = new int[] {status}; @@ -252,7 +254,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { */ public void currentEmergencyNumberList(int indicationType, android.hardware.radio.voice.EmergencyNumber[] emergencyNumberList) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); List response = new ArrayList<>(emergencyNumberList.length); for (android.hardware.radio.voice.EmergencyNumber enHal : emergencyNumberList) { @@ -279,7 +281,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param indicationType Type of radio indication */ public void enterEmergencyCallbackMode(int indicationType) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE); @@ -294,7 +296,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param indicationType Type of radio indication */ public void exitEmergencyCallbackMode(int indicationType) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE); @@ -307,7 +309,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param start true = start play ringback tone, false = stop playing ringback tone */ public void indicateRingbackTone(int indicationType, boolean start) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start); @@ -322,7 +324,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { */ public void onSupplementaryServiceIndication(int indicationType, android.hardware.radio.voice.StkCcUnsolSsResult ss) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); int num; SsData ssData = new SsData(); @@ -374,7 +376,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param msg Message string in UTF-8, if applicable */ public void onUssd(int indicationType, int ussdModeType, String msg) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType); @@ -390,7 +392,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param indicationType Type of radio indication */ public void resendIncallMute(int indicationType) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE); @@ -403,7 +405,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param state New SRVCC State */ public void srvccStateNotify(int indicationType, int state) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); int[] response = new int[] {state}; @@ -419,7 +421,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param alpha ALPHA string from UICC in UTF-8 format */ public void stkCallControlAlphaNotify(int indicationType, String alpha) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha); @@ -434,7 +436,7 @@ public class VoiceIndication extends IRadioVoiceIndication.Stub { * @param timeout Timeout value in milliseconds for setting up voice call */ public void stkCallSetup(int indicationType, long timeout) { - mRil.processIndication(RIL.VOICE_SERVICE, indicationType); + mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout); diff --git a/src/java/com/android/internal/telephony/VoiceResponse.java b/src/java/com/android/internal/telephony/VoiceResponse.java index b1ba9d98e1291d88e07bf28f4afb92a6b607e9c9..d1e060ee8114b603445f89a3a5053bfba1c44e24 100644 --- a/src/java/com/android/internal/telephony/VoiceResponse.java +++ b/src/java/com/android/internal/telephony/VoiceResponse.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE; + import android.hardware.radio.RadioError; import android.hardware.radio.RadioResponseInfo; import android.hardware.radio.voice.IRadioVoiceResponse; @@ -47,49 +49,49 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void acceptCallResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void cancelPendingUssdResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void conferenceResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void dialResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void emergencyDialResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void exitEmergencyCallbackModeResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void explicitCallTransferResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** @@ -99,7 +101,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { */ public void getCallForwardStatusResponse(RadioResponseInfo responseInfo, android.hardware.radio.voice.CallForwardInfo[] callForwardInfos) { - RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo); if (rr != null) { CallForwardInfo[] ret = new CallForwardInfo[callForwardInfos.length]; for (int i = 0; i < callForwardInfos.length; i++) { @@ -129,7 +131,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { public void getCallWaitingResponse(RadioResponseInfo responseInfo, boolean enable, int serviceClass) { RadioResponse.responseInts( - RIL.VOICE_SERVICE, mRil, responseInfo, enable ? 1 : 0, serviceClass); + HAL_SERVICE_VOICE, mRil, responseInfo, enable ? 1 : 0, serviceClass); } /** @@ -137,7 +139,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * @param status indicates CLIP status */ public void getClipResponse(RadioResponseInfo responseInfo, int status) { - RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, status); + RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, status); } /** @@ -146,7 +148,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * @param m is "m" parameter from TS 27.007 7.7 */ public void getClirResponse(RadioResponseInfo responseInfo, int n, int m) { - RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, n, m); + RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, n, m); } /** @@ -155,7 +157,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { */ public void getCurrentCallsResponse(RadioResponseInfo responseInfo, android.hardware.radio.voice.Call[] calls) { - RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo); if (rr != null) { int num = calls.length; @@ -198,7 +200,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { */ public void getLastCallFailCauseResponse(RadioResponseInfo responseInfo, android.hardware.radio.voice.LastCallFailCauseInfo fcInfo) { - RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo); if (rr != null) { LastCallFailCause ret = new LastCallFailCause(); @@ -216,7 +218,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * @param enable true for "mute enabled" and false for "mute disabled" */ public void getMuteResponse(RadioResponseInfo responseInfo, boolean enable) { - RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, enable ? 1 : 0); + RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, enable ? 1 : 0); } /** @@ -225,7 +227,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * true for Enhanced Privacy Mode (Private Long Code Mask) */ public void getPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo, boolean enable) { - RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, enable ? 1 : 0); + RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, enable ? 1 : 0); } /** @@ -233,35 +235,35 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * @param mode TTY mode */ public void getTtyModeResponse(RadioResponseInfo responseInfo, int mode) { - RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, mode); + RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, mode); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void handleStkCallSetupRequestFromSimResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void hangupConnectionResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void hangupForegroundResumeBackgroundResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void hangupWaitingOrBackgroundResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** @@ -269,7 +271,7 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * @param enable true for "vonr enabled" and false for "vonr disabled" */ public void isVoNrEnabledResponse(RadioResponseInfo responseInfo, boolean enable) { - RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo); + RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo); if (rr != null) { if (responseInfo.error == RadioError.NONE) { @@ -283,112 +285,112 @@ public class VoiceResponse extends IRadioVoiceResponse.Stub { * @param responseInfo Response info struct containing response type, serial no. and error */ public void rejectCallResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void sendBurstDtmfResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void sendCdmaFeatureCodeResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void sendDtmfResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void sendUssdResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void separateConnectionResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCallForwardResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setCallWaitingResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setClirResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setMuteResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setTtyModeResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void setVoNrEnabledResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void startDtmfResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void stopDtmfResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } /** * @param responseInfo Response info struct containing response type, serial no. and error */ public void switchWaitingOrHoldingAndActiveResponse(RadioResponseInfo responseInfo) { - RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo); + RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo); } @Override diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java old mode 100755 new mode 100644 index 08c7acdf07ba2b730e497f5e8c9172ae4b3f725e..d6ea4ad532a158aa79a39af964eb35ec787ed0a1 --- a/src/java/com/android/internal/telephony/WapPushOverSms.java +++ b/src/java/com/android/internal/telephony/WapPushOverSms.java @@ -46,6 +46,7 @@ import android.telephony.SubscriptionManager; import android.text.TextUtils; import com.android.internal.telephony.uicc.IccUtils; +import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; import com.google.android.mms.pdu.GenericPdu; @@ -392,7 +393,10 @@ public class WapPushOverSms implements ServiceConnection { // Direct the intent to only the default MMS app. If we can't find a default MMS app // then sent it to all broadcast receivers. - ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true); + UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId); + ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext, + true, userHandle); + Bundle options = null; if (componentName != null) { // Deliver MMS message only to this receiver @@ -410,9 +414,12 @@ public class WapPushOverSms implements ServiceConnection { options = bopts.toBundle(); } + if (userHandle == null) { + userHandle = UserHandle.SYSTEM; + } handler.dispatchIntent(intent, getPermissionForType(result.mimeType), getAppOpsStringPermissionForIntent(result.mimeType), options, receiver, - UserHandle.SYSTEM, subId); + userHandle, subId); return Activity.RESULT_OK; } diff --git a/src/java/com/android/internal/telephony/WspTypeDecoder.java b/src/java/com/android/internal/telephony/WspTypeDecoder.java old mode 100755 new mode 100644 diff --git a/src/java/com/android/internal/telephony/cat/AppInterface.java b/src/java/com/android/internal/telephony/cat/AppInterface.java old mode 100755 new mode 100644 diff --git a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java index 3d212709d0d54a98c77a8718b9afada70069dd01..4447c0717e0e719b3e41e28c539dc21bd7794fac 100644 --- a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java +++ b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java @@ -40,6 +40,7 @@ public class CatCmdMessage implements Parcelable { private ToneSettings mToneSettings = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private CallSettings mCallSettings = null; + private SMSSettings mSMSSettings = null; private SetupEventListSettings mSetupEventListSettings = null; private boolean mLoadIconFailed = false; @@ -61,6 +62,14 @@ public class CatCmdMessage implements Parcelable { public TextMessage callMsg; } + /** + * Container for SEND SMS command settings. + */ + public class SMSSettings { + public TextMessage smsText; + public TextMessage destAddr; + } + public class SetupEventListSettings { @UnsupportedAppUsage public int[] eventList; @@ -84,57 +93,69 @@ public class CatCmdMessage implements Parcelable { mCmdDet = cmdParams.mCmdDet; mLoadIconFailed = cmdParams.mLoadIconFailed; switch(getCmdType()) { - case SET_UP_MENU: - case SELECT_ITEM: - mMenu = ((SelectItemParams) cmdParams).mMenu; - break; - case DISPLAY_TEXT: - case SET_UP_IDLE_MODE_TEXT: - case SEND_DTMF: - case SEND_SMS: - case REFRESH: - case RUN_AT: - case SEND_SS: - case SEND_USSD: - mTextMsg = ((DisplayTextParams) cmdParams).mTextMsg; - break; - case GET_INPUT: - case GET_INKEY: - mInput = ((GetInputParams) cmdParams).mInput; - break; - case LAUNCH_BROWSER: - mTextMsg = ((LaunchBrowserParams) cmdParams).mConfirmMsg; - mBrowserSettings = new BrowserSettings(); - mBrowserSettings.url = ((LaunchBrowserParams) cmdParams).mUrl; - mBrowserSettings.mode = ((LaunchBrowserParams) cmdParams).mMode; - break; - case PLAY_TONE: - PlayToneParams params = (PlayToneParams) cmdParams; - mToneSettings = params.mSettings; - mTextMsg = params.mTextMsg; - break; - case GET_CHANNEL_STATUS: - mTextMsg = ((CallSetupParams) cmdParams).mConfirmMsg; - break; - case SET_UP_CALL: - mCallSettings = new CallSettings(); - mCallSettings.confirmMsg = ((CallSetupParams) cmdParams).mConfirmMsg; - mCallSettings.callMsg = ((CallSetupParams) cmdParams).mCallMsg; - break; - case OPEN_CHANNEL: - case CLOSE_CHANNEL: - case RECEIVE_DATA: - case SEND_DATA: - BIPClientParams param = (BIPClientParams) cmdParams; - mTextMsg = param.mTextMsg; - break; - case SET_UP_EVENT_LIST: - mSetupEventListSettings = new SetupEventListSettings(); - mSetupEventListSettings.eventList = ((SetEventListParams) cmdParams).mEventInfo; - break; - case PROVIDE_LOCAL_INFORMATION: - default: - break; + case SET_UP_MENU: + case SELECT_ITEM: + mMenu = ((SelectItemParams) cmdParams).mMenu; + break; + case SEND_SMS: + /* If cmdParams is an instanceof SendSMSParams , then it means config value + * config_stk_sms_send_support is true and the SMS should be sent by framework + */ + if (cmdParams instanceof SendSMSParams) { + mSMSSettings = new SMSSettings(); + mSMSSettings.smsText = ((SendSMSParams) cmdParams).mTextSmsMsg; + mSMSSettings.destAddr = ((SendSMSParams) cmdParams).mDestAddress; + mTextMsg = ((SendSMSParams) cmdParams).mDisplayText.mTextMsg; + } else { + mTextMsg = ((DisplayTextParams) cmdParams).mTextMsg; + } + break; + case DISPLAY_TEXT: + case SET_UP_IDLE_MODE_TEXT: + case SEND_DTMF: + case REFRESH: + case RUN_AT: + case SEND_SS: + case SEND_USSD: + mTextMsg = ((DisplayTextParams) cmdParams).mTextMsg; + break; + case GET_INPUT: + case GET_INKEY: + mInput = ((GetInputParams) cmdParams).mInput; + break; + case LAUNCH_BROWSER: + mTextMsg = ((LaunchBrowserParams) cmdParams).mConfirmMsg; + mBrowserSettings = new BrowserSettings(); + mBrowserSettings.url = ((LaunchBrowserParams) cmdParams).mUrl; + mBrowserSettings.mode = ((LaunchBrowserParams) cmdParams).mMode; + break; + case PLAY_TONE: + PlayToneParams params = (PlayToneParams) cmdParams; + mToneSettings = params.mSettings; + mTextMsg = params.mTextMsg; + break; + case GET_CHANNEL_STATUS: + mTextMsg = ((CallSetupParams) cmdParams).mConfirmMsg; + break; + case SET_UP_CALL: + mCallSettings = new CallSettings(); + mCallSettings.confirmMsg = ((CallSetupParams) cmdParams).mConfirmMsg; + mCallSettings.callMsg = ((CallSetupParams) cmdParams).mCallMsg; + break; + case OPEN_CHANNEL: + case CLOSE_CHANNEL: + case RECEIVE_DATA: + case SEND_DATA: + BIPClientParams param = (BIPClientParams) cmdParams; + mTextMsg = param.mTextMsg; + break; + case SET_UP_EVENT_LIST: + mSetupEventListSettings = new SetupEventListSettings(); + mSetupEventListSettings.eventList = ((SetEventListParams) cmdParams).mEventInfo; + break; + case PROVIDE_LOCAL_INFORMATION: + default: + break; } } @@ -145,29 +166,34 @@ public class CatCmdMessage implements Parcelable { mInput = in.readParcelable(Input.class.getClassLoader()); mLoadIconFailed = (in.readByte() == 1); switch (getCmdType()) { - case LAUNCH_BROWSER: - mBrowserSettings = new BrowserSettings(); - mBrowserSettings.url = in.readString(); - mBrowserSettings.mode = LaunchBrowserMode.values()[in.readInt()]; - break; - case PLAY_TONE: - mToneSettings = in.readParcelable(ToneSettings.class.getClassLoader()); - break; - case SET_UP_CALL: - mCallSettings = new CallSettings(); - mCallSettings.confirmMsg = in.readParcelable(TextMessage.class.getClassLoader()); - mCallSettings.callMsg = in.readParcelable(TextMessage.class.getClassLoader()); - break; - case SET_UP_EVENT_LIST: - mSetupEventListSettings = new SetupEventListSettings(); - int length = in.readInt(); - mSetupEventListSettings.eventList = new int[length]; - for (int i = 0; i < length; i++) { - mSetupEventListSettings.eventList[i] = in.readInt(); - } - break; - default: - break; + case LAUNCH_BROWSER: + mBrowserSettings = new BrowserSettings(); + mBrowserSettings.url = in.readString(); + mBrowserSettings.mode = LaunchBrowserMode.values()[in.readInt()]; + break; + case PLAY_TONE: + mToneSettings = in.readParcelable(ToneSettings.class.getClassLoader()); + break; + case SET_UP_CALL: + mCallSettings = new CallSettings(); + mCallSettings.confirmMsg = in.readParcelable(TextMessage.class.getClassLoader()); + mCallSettings.callMsg = in.readParcelable(TextMessage.class.getClassLoader()); + break; + case SET_UP_EVENT_LIST: + mSetupEventListSettings = new SetupEventListSettings(); + int length = in.readInt(); + mSetupEventListSettings.eventList = new int[length]; + for (int i = 0; i < length; i++) { + mSetupEventListSettings.eventList[i] = in.readInt(); + } + break; + case SEND_SMS: + mSMSSettings = new SMSSettings(); + mSMSSettings.smsText = in.readParcelable(SendSMSParams.class.getClassLoader()); + mSMSSettings.destAddr = in.readParcelable(SendSMSParams.class.getClassLoader()); + break; + default: + break; } } @@ -178,23 +204,29 @@ public class CatCmdMessage implements Parcelable { dest.writeParcelable(mMenu, 0); dest.writeParcelable(mInput, 0); dest.writeByte((byte) (mLoadIconFailed ? 1 : 0)); - switch(getCmdType()) { - case LAUNCH_BROWSER: - dest.writeString(mBrowserSettings.url); - dest.writeInt(mBrowserSettings.mode.ordinal()); - break; - case PLAY_TONE: - dest.writeParcelable(mToneSettings, 0); - break; - case SET_UP_CALL: - dest.writeParcelable(mCallSettings.confirmMsg, 0); - dest.writeParcelable(mCallSettings.callMsg, 0); - break; - case SET_UP_EVENT_LIST: - dest.writeIntArray(mSetupEventListSettings.eventList); - break; - default: - break; + switch (getCmdType()) { + case LAUNCH_BROWSER: + dest.writeString(mBrowserSettings.url); + dest.writeInt(mBrowserSettings.mode.ordinal()); + break; + case PLAY_TONE: + dest.writeParcelable(mToneSettings, 0); + break; + case SET_UP_CALL: + dest.writeParcelable(mCallSettings.confirmMsg, 0); + dest.writeParcelable(mCallSettings.callMsg, 0); + break; + case SET_UP_EVENT_LIST: + dest.writeIntArray(mSetupEventListSettings.eventList); + break; + case SEND_SMS: + if (mSMSSettings != null) { + dest.writeParcelable(mSMSSettings.smsText, 0); + dest.writeParcelable(mSMSSettings.destAddr, 0); + } + break; + default: + break; } } diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java index 4798a975af93800e6d92dea7261f55cfa6ee6b36..fa2b19b9d2cab3fd7d8810b6b4d66237d87e1727 100644 --- a/src/java/com/android/internal/telephony/cat/CatService.java +++ b/src/java/com/android/internal/telephony/cat/CatService.java @@ -20,26 +20,35 @@ import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListCon import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.LANGUAGE_SELECTION_EVENT; import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.USER_ACTIVITY_EVENT; +import android.app.Activity; import android.app.ActivityManager; +import android.app.PendingIntent; import android.app.backup.BackupManager; import android.compat.annotation.UnsupportedAppUsage; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources.NotFoundException; import android.os.AsyncResult; import android.os.Build; import android.os.Handler; +import android.os.HandlerThread; import android.os.LocaleList; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; +import com.android.internal.telephony.ProxyController; +import com.android.internal.telephony.SmsController; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.IccCardStatus.CardState; import com.android.internal.telephony.uicc.IccFileHandler; @@ -140,12 +149,21 @@ public class CatService extends Handler implements AppInterface { static final String STK_DEFAULT = "Default Message"; + private static final String SMS_DELIVERY_ACTION = + "com.android.internal.telephony.cat.SMS_DELIVERY_ACTION"; + private static final String SMS_SENT_ACTION = + "com.android.internal.telephony.cat.SMS_SENT_ACTION"; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private int mSlotId; + private static HandlerThread sCatServiceThread; /* For multisim catservice should not be singleton */ private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, - Context context, IccFileHandler fh, UiccProfile uiccProfile, int slotId) { + Context context, IccFileHandler fh, UiccProfile uiccProfile, int slotId, + Looper looper) { + //creating new thread to avoid deadlock conditions with the framework thread. + super(looper); if (ci == null || ca == null || ir == null || context == null || fh == null || uiccProfile == null) { throw new NullPointerException( @@ -189,6 +207,9 @@ public class CatService extends Handler implements AppInterface { CatLog.d(this, "Running CAT service on Slotid: " + mSlotId + ". STK app installed:" + mStkAppInstalled); + + mContext.registerReceiver(mSmsBroadcastReceiver, new IntentFilter(SMS_DELIVERY_ACTION)); + mContext.registerReceiver(mSmsBroadcastReceiver, new IntentFilter(SMS_SENT_ACTION)); } /** @@ -202,6 +223,10 @@ public class CatService extends Handler implements AppInterface { */ public static CatService getInstance(CommandsInterface ci, Context context, UiccProfile uiccProfile, int slotId) { + if (sCatServiceThread == null) { + sCatServiceThread = new HandlerThread("CatServiceThread"); + sCatServiceThread.start(); + } UiccCardApplication ca = null; IccFileHandler fh = null; IccRecords ir = null; @@ -229,8 +254,8 @@ public class CatService extends Handler implements AppInterface { || uiccProfile == null) { return null; } - - sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId); + sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId, + sCatServiceThread.getLooper()); } else if ((ir != null) && (mIccRecords != ir)) { if (mIccRecords != null) { mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]); @@ -449,8 +474,49 @@ public class CatService extends Handler implements AppInterface { ((DisplayTextParams)cmdParams).mTextMsg.text = null; } break; - case SEND_DTMF: case SEND_SMS: + /* If cmdParams is an instanceof SendSMSParams , then it means config value + * config_stk_sms_send_support is true and the SMS should be sent by framework + */ + if (cmdParams instanceof SendSMSParams) { + String text = null, destAddr = null; + if (((SendSMSParams) cmdParams).mTextSmsMsg != null) { + text = ((SendSMSParams) cmdParams).mTextSmsMsg.text; + } + if (((SendSMSParams) cmdParams).mDestAddress != null) { + destAddr = ((SendSMSParams) cmdParams).mDestAddress.text; + } + if (text != null && destAddr != null) { + ProxyController proxyController = ProxyController.getInstance(mContext); + SubscriptionManager subscriptionManager = (SubscriptionManager) + mContext.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + SubscriptionInfo subInfo = + subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex( + mSlotId); + if (subInfo != null) { + sendStkSms(text, destAddr, subInfo.getSubscriptionId(), cmdParams, + proxyController); + } else { + sendTerminalResponse(cmdParams.mCmdDet, + ResultCode.CMD_DATA_NOT_UNDERSTOOD, false, 0x00, null); + CatLog.d(this, "Subscription info is null"); + } + } else { + sendTerminalResponse(cmdParams.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD, + false, 0x00, null); + CatLog.d(this, "Sms text or Destination Address is null"); + } + } else { + if ((((DisplayTextParams) cmdParams).mTextMsg.text != null) + && (((DisplayTextParams) cmdParams).mTextMsg.text.equals( + STK_DEFAULT))) { + message = mContext.getText(com.android.internal.R.string.sending); + ((DisplayTextParams) cmdParams).mTextMsg.text = message.toString(); + } + } + break; + case SEND_DTMF: case SEND_SS: case SEND_USSD: if ((((DisplayTextParams)cmdParams).mTextMsg.text != null) @@ -538,6 +604,94 @@ public class CatService extends Handler implements AppInterface { broadcastCatCmdIntent(cmdMsg); } + /** + * Used to send STK based sms via CATService + * @param text The message body + * @param destAddr The destination Address + * @param subId Subscription Id + * @param cmdParams Send SMS Command Params + * @param proxyController ProxyController + * @hide + */ + public void sendStkSms(String text, String destAddr, int subId, CommandParams cmdParams, + ProxyController proxyController) { + PendingIntent sentPendingIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent(SMS_SENT_ACTION) + .putExtra("cmdDetails", cmdParams.mCmdDet) + .setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + PendingIntent deliveryPendingIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent(SMS_DELIVERY_ACTION) + .putExtra("cmdDetails", cmdParams.mCmdDet) + .setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + SmsController smsController = proxyController.getSmsController(); + smsController.sendTextForSubscriber(subId, mContext.getOpPackageName(), + mContext.getAttributionTag(), destAddr, null, text, sentPendingIntent, + deliveryPendingIntent, false, 0L, true, true); + } + + /** + * BroadcastReceiver class to handle error and success cases of + * SEND and DELIVERY pending intents used for sending of STK SMS + */ + @VisibleForTesting + public final BroadcastReceiver mSmsBroadcastReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + CommandDetails commandDetails = (CommandDetails) intent.getExtra("cmdDetails"); + if (intent.getAction().equals(SMS_SENT_ACTION)) { + int resultCode = getResultCode(); + ResultCode terminalResponseResultCode = ResultCode.NETWORK_CRNTLY_UNABLE_TO_PROCESS; + CatLog.d(this, "STK SMS errorCode : " + resultCode); + int additionalInfo = 0; + if (resultCode != Activity.RESULT_OK) { + /** + * The Terminal Response Result code is assigned as per Section 12.12.3 + * and 12.12.5 TS 101.267. The Result code SMS_RP_ERROR is added in Ims Case + * and additional information is added as per RP-Cause Values in TS 124.011. + * The Result code NETWORK_CRNTLY_UNABLE_TO_PROCESS is added in non-Ims Case + * and additional information added as per cause values in TS 04.08. + */ + if (intent.hasExtra("ims") && intent.getBooleanExtra("ims", false)) { + terminalResponseResultCode = ResultCode.SMS_RP_ERROR; + //Additional information's 8th bit is 0 as per section 12.12.5 of TS 101.267 + if (intent.hasExtra("errorCode")) { + additionalInfo = (int) intent.getExtra("errorCode"); + if ((additionalInfo & 0x80) != 0) additionalInfo = 0; + } + } else { + //Additional information's 8th bit is 1 as per section 12.12.3 of TS 101.267 + if (intent.hasExtra("errorCode")) { + additionalInfo = (int) intent.getExtra("errorCode"); + additionalInfo |= 0x80; + } + } + CatLog.d(this, "Error delivering STK SMS errorCode : " + additionalInfo + + " terminalResponseResultCode = " + terminalResponseResultCode); + sendTerminalResponse(commandDetails, terminalResponseResultCode, + true, additionalInfo, null); + } else { + CatLog.d(this, " STK SMS sent successfully "); + } + } + if (intent.getAction().equals(SMS_DELIVERY_ACTION)) { + int resultCode = getResultCode(); + switch (resultCode) { + case Activity.RESULT_OK: + sendTerminalResponse(commandDetails, ResultCode.OK, false, 0, null); + CatLog.d(this, " STK SMS delivered successfully "); + break; + default: + CatLog.d(this, "Error delivering STK SMS : " + resultCode); + sendTerminalResponse(commandDetails, + ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS, false, + 0, null); + } + } + } + }; private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) { Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); @@ -810,16 +964,9 @@ public class CatService extends Handler implements AppInterface { //TODO Need to take care for MSIM public static AppInterface getInstance() { int slotId = PhoneConstants.DEFAULT_SLOT_INDEX; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - if (SubscriptionManagerService.getInstance() != null) { - slotId = SubscriptionManagerService.getInstance().getSlotIndex( - SubscriptionManagerService.getInstance().getDefaultSubId()); - } - } else { - SubscriptionController sControl = SubscriptionController.getInstance(); - if (sControl != null) { - slotId = sControl.getSlotIndex(sControl.getDefaultSubId()); - } + if (SubscriptionManagerService.getInstance() != null) { + slotId = SubscriptionManagerService.getInstance().getSlotIndex( + SubscriptionManagerService.getInstance().getDefaultSubId()); } return getInstance(null, null, null, slotId); } @@ -854,7 +1001,11 @@ public class CatService extends Handler implements AppInterface { } } } - mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data)); + if (mMsgDecoder != null) { + mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data)); + } else { + CatLog.e(this, "Error in handleMessage (" + msg.what + ") mMsgDecoder is NULL"); + } break; case MSG_ID_CALL_SETUP: mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null)); diff --git a/src/java/com/android/internal/telephony/cat/CommandParams.java b/src/java/com/android/internal/telephony/cat/CommandParams.java old mode 100755 new mode 100644 index b9de4d1f2d87b865087f8c4ab2aec9df427af233..8530ee2ec7ec471b1460840a84e602446ac808c4 --- a/src/java/com/android/internal/telephony/cat/CommandParams.java +++ b/src/java/com/android/internal/telephony/cat/CommandParams.java @@ -22,16 +22,16 @@ import android.os.Build; /** * Container class for proactive command parameters. - * + * @hide */ -class CommandParams { +public class CommandParams { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) CommandDetails mCmdDet; // Variable to track if an optional icon load has failed. boolean mLoadIconFailed = false; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - CommandParams(CommandDetails cmdDet) { + public CommandParams(CommandDetails cmdDet) { mCmdDet = cmdDet; } diff --git a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java index 7fbebfad8e473ac868b19ce31709bae14d9775ba..65f3c4ac133aeff99b1d90693acc59126899fad9 100644 --- a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java +++ b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java @@ -29,6 +29,7 @@ import android.graphics.Bitmap; import android.os.Build; import android.os.Handler; import android.os.Message; +import android.telephony.SmsMessage; import android.text.TextUtils; import com.android.internal.telephony.GsmAlphabet; @@ -40,9 +41,9 @@ import java.util.Locale; /** * Factory class, used for decoding raw byte arrays, received from baseband, * into a CommandParams object. - * + * @hide */ -class CommandParamsFactory extends Handler { +public class CommandParamsFactory extends Handler { private static CommandParamsFactory sInstance = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private IconLoader mIconLoader; @@ -53,6 +54,7 @@ class CommandParamsFactory extends Handler { private String mSavedLanguage; private String mRequestedLanguage; private boolean mNoAlphaUsrCnf = false; + private boolean mStkSmsSendViaTelephony = false; // constants static final int MSG_ID_LOAD_ICON_DONE = 1; @@ -86,7 +88,15 @@ class CommandParamsFactory extends Handler { private static final int MAX_GSM7_DEFAULT_CHARS = 239; private static final int MAX_UCS2_CHARS = 118; - static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller, + /** + * Returns a singleton instance of CommandParamsFactory + * @param caller Class used for queuing raw ril messages, decoding them into + * CommandParams objects and sending the result back to the CAT Service. + * @param fh IccFileHandler Object + * @param context The Context + * @return CommandParamsFactory instance + */ + public static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller, IccFileHandler fh, Context context) { if (sInstance != null) { return sInstance; @@ -106,6 +116,12 @@ class CommandParamsFactory extends Handler { } catch (NotFoundException e) { mNoAlphaUsrCnf = false; } + try { + mStkSmsSendViaTelephony = context.getResources().getBoolean( + com.android.internal.R.bool.config_stk_sms_send_support); + } catch (NotFoundException e) { + mStkSmsSendViaTelephony = false; + } } private CommandDetails processCommandDetails(List ctlvs) { @@ -187,8 +203,14 @@ class CommandParamsFactory extends Handler { case GET_INPUT: cmdPending = processGetInput(cmdDet, ctlvs); break; - case SEND_DTMF: case SEND_SMS: + if (mStkSmsSendViaTelephony) { + cmdPending = processSMSEventNotify(cmdDet, ctlvs); + } else { + cmdPending = processEventNotify(cmdDet, ctlvs); + } + break; + case SEND_DTMF: case REFRESH: case RUN_AT: case SEND_SS: @@ -735,6 +757,62 @@ class CommandParamsFactory extends Handler { return false; } + + /** + * Processes SMS_EVENT_NOTIFY message from baseband. + * + * Method extracts values such as Alpha Id,Icon Id,Sms Tpdu etc from the ComprehensionTlv, + * in order to create the CommandParams i.e. SendSMSParams. + * + * @param cmdDet Command Details container object. + * @param ctlvs List of ComprehensionTlv objects following Command Details + * object and Device Identities object within the proactive command + * @return true if the command is processing is pending and additional + * asynchronous processing is required. + * @hide + */ + public boolean processSMSEventNotify(CommandDetails cmdDet, + List ctlvs) throws ResultException { + CatLog.d(this, "processSMSEventNotify"); + + TextMessage textMsg = new TextMessage(); + IconId iconId = null; + + ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, + ctlvs); + /* Retrieves alpha identifier from an Alpha Identifier COMPREHENSION-TLV object. + * + * String corresponding to the alpha identifier is obtained and saved as part of + * the DisplayTextParams. + */ + textMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf); + + ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs); + if (ctlv != null) { + // Retrieves icon id from the Icon Identifier COMPREHENSION-TLV object + iconId = ValueParser.retrieveIconId(ctlv); + textMsg.iconSelfExplanatory = iconId.selfExplanatory; + } + + textMsg.responseNeeded = false; + DisplayTextParams displayTextParams = new DisplayTextParams(cmdDet, textMsg); + ComprehensionTlv ctlvTpdu = searchForTag(ComprehensionTlvTag.SMS_TPDU, + ctlvs); + // Retrieves smsMessage from the SMS TPDU COMPREHENSION-TLV object + SmsMessage smsMessage = ValueParser.retrieveTpduAsSmsMessage(ctlvTpdu); + if (smsMessage != null) { + TextMessage smsText = new TextMessage(); + // Obtains the sms message content. + smsText.text = smsMessage.getMessageBody(); + TextMessage destAddr = new TextMessage(); + // Obtains the destination Address. + destAddr.text = smsMessage.getRecipientAddress(); + mCmdParams = new SendSMSParams(cmdDet, smsText, destAddr, displayTextParams); + return false; + } + return true; + } + /** * Processes SET_UP_EVENT_LIST proactive command from the SIM card. * diff --git a/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java index 5542b656b35b8165104a7248978dd7e630a934f1..416c66926ad95ce36e14543b5deddc5503f16f93 100644 --- a/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java +++ b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java @@ -50,7 +50,7 @@ public class ComprehensionTlv { * @param data Byte array containing the value * @param valueIndex Index in data at which the value starts */ - protected ComprehensionTlv(int tag, boolean cr, int length, byte[] data, + public ComprehensionTlv(int tag, boolean cr, int length, byte[] data, int valueIndex) { mTag = tag; mCr = cr; diff --git a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java old mode 100755 new mode 100644 index c25b59edac18f6b6d497a8a6c580d013e2e040c2..4b10cae7e061e5f5dbab1fbf3e958a9ed3408752 --- a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java +++ b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java @@ -32,8 +32,9 @@ import com.android.internal.util.StateMachine; /** * Class used for queuing raw ril messages, decoding them into CommanParams * objects and sending the result back to the CAT Service. + * @hide */ -class RilMessageDecoder extends StateMachine { +public class RilMessageDecoder extends StateMachine { // constants private static final int CMD_START = 1; diff --git a/src/java/com/android/internal/telephony/cat/SendSMSParams.java b/src/java/com/android/internal/telephony/cat/SendSMSParams.java new file mode 100644 index 0000000000000000000000000000000000000000..f5108f04c8577e3a3701149e46e62a8f8899e832 --- /dev/null +++ b/src/java/com/android/internal/telephony/cat/SendSMSParams.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.cat; + +class SendSMSParams extends CommandParams { + + TextMessage mTextSmsMsg; + TextMessage mDestAddress; + DisplayTextParams mDisplayText; + + SendSMSParams(CommandDetails cmdDet, TextMessage textMsg, TextMessage destAddress, + DisplayTextParams displayText) { + super(cmdDet); + mTextSmsMsg = textMsg; + mDestAddress = destAddress; + mDisplayText = displayText; + } + +} diff --git a/src/java/com/android/internal/telephony/cat/ValueParser.java b/src/java/com/android/internal/telephony/cat/ValueParser.java index 7c0913629fc609a44ea3744b719344b4068bd9c1..bd17f48feda7a1784e36c6bb52b8860da06ea96d 100644 --- a/src/java/com/android/internal/telephony/cat/ValueParser.java +++ b/src/java/com/android/internal/telephony/cat/ValueParser.java @@ -18,16 +18,24 @@ package com.android.internal.telephony.cat; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import android.telephony.SmsMessage; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.cat.Duration.TimeUnit; import com.android.internal.telephony.uicc.IccUtils; +import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -abstract class ValueParser { + +/** + * Util class that parses different entities from the ctlvs ComprehensionTlv List + * @hide + */ +public abstract class ValueParser { /** * Search for a Command Details object from a list. @@ -352,4 +360,42 @@ abstract class ValueParser { throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD); } } + + /** + * Retrieve's the tpdu from the ctlv and creates the SmsMessage from pdu. + * @param ctlv ComprehensionTlv value + * @return message SmsMessage to retrieve the destAddress and Text + * @throws ResultException + * @hide + */ + public static SmsMessage retrieveTpduAsSmsMessage(ComprehensionTlv ctlv) + throws ResultException { + if (ctlv != null) { + byte[] rawValue = ctlv.getRawValue(); + int valueIndex = ctlv.getValueIndex(); + int length = ctlv.getLength(); + if (length != 0) { + try { + byte[] pdu = Arrays.copyOfRange(rawValue, valueIndex, (valueIndex + length)); + ByteArrayOutputStream bo = new ByteArrayOutputStream(pdu.length + 1); + /* Framework's TPdu Parser expects the TPdu be prepended with SC-Address. + * else the parser will throw an exception. So prepending TPdu with 0, + * which indicates that there is no SC address and its length is 0. + * This way Parser will skip parsing for SC-Address + */ + bo.write(0x00); + bo.write(pdu, 0, pdu.length); + byte[] frameworkPdu = bo.toByteArray(); + //ToDO handle for 3GPP2 format bug: b/243123533 + SmsMessage message = SmsMessage.createFromPdu(frameworkPdu, + SmsMessage.FORMAT_3GPP); + return message; + } catch (IndexOutOfBoundsException e) { + throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD); + } + } + } + return null; + } + } diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java index eb5f866b8e91aa06e87ec90c7102448e401fc502..784c9743f858a297eb5566ba646760f56f161114 100644 --- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java +++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; +import android.os.Looper; import android.os.Message; import android.os.RemoteCallback; import android.os.SystemProperties; @@ -77,8 +78,8 @@ public class CdmaInboundSmsHandler extends InboundSmsHandler { * Create a new inbound SMS handler for CDMA. */ private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, - Phone phone, CdmaSMSDispatcher smsDispatcher) { - super("CdmaInboundSmsHandler", context, storageMonitor, phone); + Phone phone, CdmaSMSDispatcher smsDispatcher, Looper looper) { + super("CdmaInboundSmsHandler", context, storageMonitor, phone, looper); mSmsDispatcher = smsDispatcher; phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null); @@ -169,9 +170,10 @@ public class CdmaInboundSmsHandler extends InboundSmsHandler { * Wait for state machine to enter startup state. We can't send any messages until then. */ public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context, - SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher) { + SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher, + Looper looper) { CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor, - phone, smsDispatcher); + phone, smsDispatcher, looper); handler.start(); return handler; } @@ -194,7 +196,8 @@ public class CdmaInboundSmsHandler extends InboundSmsHandler { * @return true if the message was handled here; false to continue processing */ @Override - protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource) { + protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource, + int token) { SmsMessage sms = (SmsMessage) smsb; boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()); diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java b/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java old mode 100755 new mode 100644 index b31df59151190e2a9742dee452e4bc04bba31da7..24ee56dd5cfe1622b823893f79ba63f04d9f1afd --- a/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java +++ b/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java @@ -17,6 +17,8 @@ package com.android.internal.telephony.cdma; +import java.util.Objects; + /** * CdmaSmsBroadcastConfigInfo defines one configuration of Cdma Broadcast * Message to be received by the ME @@ -84,4 +86,22 @@ public class CdmaSmsBroadcastConfigInfo { mFromServiceCategory + ", " + mToServiceCategory + "] " + (isSelected() ? "ENABLED" : "DISABLED"); } + + @Override + public int hashCode() { + return Objects.hash(mFromServiceCategory, mToServiceCategory, mLanguage, mSelected); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CdmaSmsBroadcastConfigInfo)) { + return false; + } + + CdmaSmsBroadcastConfigInfo other = (CdmaSmsBroadcastConfigInfo) obj; + + return mFromServiceCategory == other.mFromServiceCategory + && mToServiceCategory == other.mToServiceCategory + && mLanguage == other.mLanguage && mSelected == other.mSelected; + } } diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java index 69748372b7a07cbf3600b878d6b507584a88a36e..267f70b389bc4be5b4738ed99aafff5f87dd52a1 100644 --- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java +++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java @@ -19,19 +19,19 @@ package com.android.internal.telephony.data; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.StringDef; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.AsyncResult; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Message; import android.os.PersistableBundle; import android.os.Registrant; import android.os.RegistrantList; import android.os.RemoteException; -import android.os.SystemProperties; import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; @@ -52,14 +52,11 @@ import android.util.LocalLog; import android.util.SparseArray; import com.android.internal.telephony.Phone; -import com.android.internal.telephony.RIL; import com.android.internal.telephony.SlidingWindowEventCounter; 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.ArrayList; import java.util.Arrays; import java.util.List; @@ -77,35 +74,9 @@ import java.util.stream.Collectors; */ public class AccessNetworksManager extends Handler { private static final boolean DBG = false; - public static final String SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE = - "ro.telephony.iwlan_operation_mode"; - @Retention(RetentionPolicy.SOURCE) - @StringDef(prefix = {"IWLAN_OPERATION_MODE_"}, - value = { - IWLAN_OPERATION_MODE_DEFAULT, - IWLAN_OPERATION_MODE_LEGACY, - IWLAN_OPERATION_MODE_AP_ASSISTED}) - public @interface IwlanOperationMode {} - - /** - * IWLAN default mode. On device that has IRadio 1.4 or above, it means - * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.3 or below, it means - * {@link #IWLAN_OPERATION_MODE_LEGACY}. - */ - public static final String IWLAN_OPERATION_MODE_DEFAULT = "default"; - - /** - * IWLAN legacy mode. IWLAN is completely handled by the modem, and when the device is on - * IWLAN, modem reports IWLAN as a RAT. - */ - public static final String IWLAN_OPERATION_MODE_LEGACY = "legacy"; - - /** - * IWLAN application processor assisted mode. IWLAN is handled by the bound IWLAN data service - * and network service separately. - */ - public static final String IWLAN_OPERATION_MODE_AP_ASSISTED = "AP-assisted"; + /** Event to guide a transport type for initial data connection of emergency data network. */ + private static final int EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY = 1; /** * The counters to detect frequent QNS attempt to change preferred network transport by ApnType. @@ -190,6 +161,19 @@ public class AccessNetworksManager extends Handler { } } + @Override + public void handleMessage(@NonNull Message msg) { + switch (msg.what) { + case EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY: + AsyncResult ar = (AsyncResult) msg.obj; + int transport = (int) ar.result; + onEmergencyDataNetworkPreferredTransportChanged(transport); + break; + default: + loge("Unexpected event " + msg.what); + } + } + private class AccessNetworksManagerDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { @@ -312,6 +296,20 @@ public class AccessNetworksManager extends Handler { } } + private void onEmergencyDataNetworkPreferredTransportChanged( + @AccessNetworkConstants.TransportType int transportType) { + try { + logl("onEmergencyDataNetworkPreferredTransportChanged: " + + AccessNetworkConstants.transportTypeToString(transportType)); + if (mIQualifiedNetworksService != null) { + mIQualifiedNetworksService.reportEmergencyDataNetworkPreferredTransportChanged( + mPhone.getPhoneId(), transportType); + } + } catch (Exception ex) { + loge("onEmergencyDataNetworkPreferredTransportChanged: ", ex); + } + } + /** * Access networks manager callback. This should be only used by {@link DataNetworkController}. */ @@ -346,36 +344,21 @@ public class AccessNetworksManager extends Handler { Context.CARRIER_CONFIG_SERVICE); mLogTag = "ANM-" + mPhone.getPhoneId(); mApnTypeToQnsChangeNetworkCounter = new SparseArray<>(); - - if (isInLegacyMode()) { - log("operates in legacy mode."); - // For legacy mode, WWAN is the only transport to handle all data connections, even - // the IWLAN ones. - mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN}; - } else { - log("operates in AP-assisted mode."); - mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN, - AccessNetworkConstants.TRANSPORT_TYPE_WLAN}; - - // bindQualifiedNetworksService posts real work to handler thread. So here we can - // let the callback execute in binder thread to avoid post twice. - mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run, - (slotIndex, subId, carrierId, specificCarrierId) -> { - if (slotIndex != mPhone.getPhoneId()) return; - // We should wait for carrier config changed event because the target - // binding - // package name can come from the carrier config. Note that we still get - // this - // event even when SIM is absent. - if (DBG) { - log( - "Carrier config changed. Try to bind qualified network " - + "service."); - } - bindQualifiedNetworksService(); - }); - bindQualifiedNetworksService(); - } + mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN}; + + // bindQualifiedNetworksService posts real work to handler thread. So here we can + // let the callback execute in binder thread to avoid post twice. + mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run, + (slotIndex, subId, carrierId, specificCarrierId) -> { + if (slotIndex != mPhone.getPhoneId()) return; + // We should wait for carrier config changed event because the target binding + // package name can come from the carrier config. Note that we still get this + // event even when SIM is absent. + if (DBG) log("Carrier config changed. Try to bind qualified network service."); + bindQualifiedNetworksService(); + }); + bindQualifiedNetworksService(); // Using post to delay the registering because data retry manager and data config // manager instances are created later than access networks manager. @@ -403,6 +386,8 @@ public class AccessNetworksManager extends Handler { mApnTypeToQnsChangeNetworkCounter.clear(); } }); + mPhone.registerForEmergencyDomainSelected( + this, EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY, null); }); } @@ -478,8 +463,7 @@ public class AccessNetworksManager extends Handler { /** * Get the qualified network service package. * - * @return package name of the qualified networks service package. Return empty string when in - * legacy mode (i.e. Dedicated IWLAN data/network service is not supported). + * @return package name of the qualified networks service package. */ private String getQualifiedNetworksServicePackageName() { // Read package name from the resource @@ -568,30 +552,9 @@ public class AccessNetworksManager extends Handler { } /** - * @return {@code true} if the device operates in legacy mode, otherwise {@code false}. + * @return The available transports. */ - public boolean isInLegacyMode() { - // Get IWLAN operation mode from the system property. If the system property is configured - // to default or not configured, the mode is tied to IRadio version. For 1.4 or above, it's - // AP-assisted mode, for 1.3 or below, it's legacy mode. - String mode = SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE); - - if (mode.equals(IWLAN_OPERATION_MODE_AP_ASSISTED)) { - return false; - } else if (mode.equals(IWLAN_OPERATION_MODE_LEGACY)) { - return true; - } - - return mPhone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4); - } - - /** - * @return The available transports. Note that on legacy devices, the only available transport - * would be WWAN only. If the device is configured as AP-assisted mode, the available transport - * will always be WWAN and WLAN (even if the device is not camped on IWLAN). - * See {@link #isInLegacyMode()} for mode details. - */ - public synchronized @NonNull int[] getAvailableTransports() { + public @NonNull int[] getAvailableTransports() { return mAvailableTransports; } @@ -626,11 +589,6 @@ public class AccessNetworksManager extends Handler { * @return The preferred transport. */ public @TransportType int getPreferredTransport(@ApnType int apnType) { - // In legacy mode, always preferred on cellular. - if (isInLegacyMode()) { - return AccessNetworkConstants.TRANSPORT_TYPE_WWAN; - } - return mPreferredTransports.get(apnType) == null ? AccessNetworkConstants.TRANSPORT_TYPE_WWAN : mPreferredTransports.get(apnType); } @@ -733,9 +691,6 @@ public class AccessNetworksManager extends Handler { } pw.decreaseIndent(); - pw.println("isInLegacy=" + isInLegacyMode()); - pw.println("IWLAN operation mode=" - + SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE)); pw.println("Local logs="); pw.increaseIndent(); mLocalLog.dump(fd, pw, args); diff --git a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java index aa830aeea4cb680453ef5c5f28f5360868ed78fe..c1d12033006808eee6d11bc29b9a17a258976b98 100644 --- a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java +++ b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java @@ -40,7 +40,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConfigurationManager; import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; @@ -166,17 +165,9 @@ public class CellularNetworkValidator { private String getValidationNetworkIdentity(int subId) { if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null; - Phone phone; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - if (SubscriptionManagerService.getInstance() == null) return null; - phone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance() - .getPhoneId(subId)); - } else { - SubscriptionController subController = SubscriptionController.getInstance(); - if (subController == null) return null; - phone = PhoneFactory.getPhone(subController.getPhoneId(subId)); - } - + if (SubscriptionManagerService.getInstance() == null) return null; + Phone phone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance() + .getPhoneId(subId)); if (phone == null || phone.getServiceState() == null) return null; NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo( @@ -267,20 +258,12 @@ public class CellularNetworkValidator { // If it's already validating the same subscription, do nothing. if (subId == mSubId) return; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(subId); - if (subInfo == null || !subInfo.isActive()) { - logd("Failed to start validation. Inactive subId " + subId); - callback.onValidationDone(false, subId); - return; - } - } else { - if (!SubscriptionController.getInstance().isActiveSubId(subId)) { - logd("Failed to start validation. Inactive subId " + subId); - callback.onValidationDone(false, subId); - return; - } + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(subId); + if (subInfo == null || !subInfo.isActive()) { + logd("Failed to start validation. Inactive subId " + subId); + callback.onValidationDone(false, subId); + return; } if (isValidating()) { diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java index 731aac75f3756ae2ff0f723ef64f748fec4819e2..78450a81edcf4e7dc63216a510583ee542ca8d21 100644 --- a/src/java/com/android/internal/telephony/data/DataConfigManager.java +++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java @@ -72,12 +72,6 @@ public class DataConfigManager extends Handler { /** The default timeout in ms for data network stuck in a transit state. */ private static final int DEFAULT_NETWORK_TRANSIT_STATE_TIMEOUT_MS = 300000; - /** Default time threshold in ms to define a internet connection status to be stable. */ - public static int DEFAULT_AUTO_DATA_SWITCH_STABILITY_TIME_MS = 10000; - - /** The max number of retries when a pre-switching validation fails. */ - public static int DEFAULT_AUTO_DATA_SWITCH_MAX_RETRY = 7; - /** Event for carrier config changed. */ private static final int EVENT_CARRIER_CONFIG_CHANGED = 1; @@ -268,18 +262,6 @@ public class DataConfigManager extends Handler { */ private boolean mIsApnConfigAnomalyReportEnabled; - /** - * 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; - - /** - * The maximum number of retries when a pre-switching validation fails. - */ - private int mAutoDataSwitchValidationMaxRetry; - private @NonNull final Phone mPhone; private @NonNull final String mLogTag; @@ -438,12 +420,6 @@ public class DataConfigManager extends Handler { KEY_ANOMALY_NETWORK_HANDOVER_TIMEOUT, DEFAULT_NETWORK_TRANSIT_STATE_TIMEOUT_MS); mIsApnConfigAnomalyReportEnabled = properties.getBoolean( KEY_ANOMALY_APN_CONFIG_ENABLED, false); - mAutoDataSwitchAvailabilityStabilityTimeThreshold = properties.getInt( - KEY_AUTO_DATA_SWITCH_AVAILABILITY_STABILITY_TIME_THRESHOLD, - DEFAULT_AUTO_DATA_SWITCH_STABILITY_TIME_MS); - mAutoDataSwitchValidationMaxRetry = properties.getInt( - KEY_AUTO_DATA_SWITCH_VALIDATION_MAX_RETRY, - DEFAULT_AUTO_DATA_SWITCH_MAX_RETRY); } /** @@ -712,6 +688,12 @@ public class DataConfigManager extends Handler { return mShouldKeepNetworkUpInNonVops; } + /** {@code True} requires ping test to pass on the target slot before switching to it.*/ + public boolean isPingTestBeforeAutoDataSwitchRequired() { + return mResources.getBoolean(com.android.internal.R.bool + .auto_data_switch_ping_test_before_switch); + } + /** * @return Whether {@link NetworkCapabilities#NET_CAPABILITY_TEMPORARILY_NOT_METERED} * is supported by the carrier. @@ -939,7 +921,8 @@ public class DataConfigManager extends Handler { * @return The maximum number of retries when a validation for switching failed. */ public int getAutoDataSwitchValidationMaxRetry() { - return mAutoDataSwitchValidationMaxRetry; + return mResources.getInteger(com.android.internal.R.integer + .auto_data_switch_validation_max_retry); } /** @@ -948,7 +931,8 @@ public class DataConfigManager extends Handler { * auto switch feature disabled. */ public long getAutoDataSwitchAvailabilityStabilityTimeThreshold() { - return mAutoDataSwitchAvailabilityStabilityTimeThreshold; + return mResources.getInteger(com.android.internal.R.integer + .auto_data_switch_availability_stability_time_threshold_millis); } /** @@ -1006,7 +990,7 @@ public class DataConfigManager extends Handler { * @return {@code true} if tearing down IMS data network should be delayed until the voice call * ends. */ - public boolean isImsDelayTearDownEnabled() { + public boolean isImsDelayTearDownUntilVoiceCallEndEnabled() { return mCarrierConfig.getBoolean( CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL); } @@ -1331,9 +1315,9 @@ public class DataConfigManager extends Handler { pw.println("mNetworkDisconnectingTimeout=" + mNetworkDisconnectingTimeout); pw.println("mNetworkHandoverTimeout=" + mNetworkHandoverTimeout); pw.println("mIsApnConfigAnomalyReportEnabled=" + mIsApnConfigAnomalyReportEnabled); - pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold=" - + mAutoDataSwitchAvailabilityStabilityTimeThreshold); - pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry); + pw.println("getAutoDataSwitchAvailabilityStabilityTimeThreshold=" + + getAutoDataSwitchAvailabilityStabilityTimeThreshold()); + pw.println("getAutoDataSwitchValidationMaxRetry=" + getAutoDataSwitchValidationMaxRetry()); pw.println("Metered APN types=" + mMeteredApnTypes.stream() .map(ApnSetting::getApnTypeString).collect(Collectors.joining(","))); pw.println("Roaming metered APN types=" + mRoamingMeteredApnTypes.stream() @@ -1344,6 +1328,8 @@ public class DataConfigManager extends Handler { .stream().map(DataUtils::networkCapabilityToString) .collect(Collectors.joining(","))); pw.println("mShouldKeepNetworkUpInNoVops=" + mShouldKeepNetworkUpInNonVops); + pw.println("isPingTestBeforeAutoDataSwitchRequired=" + + isPingTestBeforeAutoDataSwitchRequired()); pw.println("Unmetered network types=" + String.join(",", mUnmeteredNetworkTypes)); pw.println("Roaming unmetered network types=" + String.join(",", mRoamingUnmeteredNetworkTypes)); @@ -1368,7 +1354,8 @@ public class DataConfigManager extends Handler { + shouldPersistIwlanDataNetworksWhenDataServiceRestarted()); pw.println("Bandwidth estimation source=" + mResources.getString( com.android.internal.R.string.config_bandwidthEstimateSource)); - pw.println("isDelayTearDownImsEnabled=" + isImsDelayTearDownEnabled()); + pw.println("isImsDelayTearDownUntilVoiceCallEndEnabled=" + + isImsDelayTearDownUntilVoiceCallEndEnabled()); pw.println("isEnhancedIwlanHandoverCheckEnabled=" + isEnhancedIwlanHandoverCheckEnabled()); pw.println("isTetheringProfileDisabledForRoaming=" + isTetheringProfileDisabledForRoaming()); diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java index f13e66b611aaec5716e684069bcf1676ace6b0ba..2ba3fe46eac8ab5364aaa56aeed397b26aa49037 100644 --- a/src/java/com/android/internal/telephony/data/DataEvaluation.java +++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java @@ -323,7 +323,9 @@ public class DataEvaluation { /** Only one data network is allowed at one time. */ ONLY_ALLOWED_SINGLE_NETWORK(true), /** Data enabled settings are not ready. */ - DATA_SETTINGS_NOT_READY(true); + DATA_SETTINGS_NOT_READY(true), + /** Handover max retry stopped but network is not on the preferred transport. */ + HANDOVER_RETRY_STOPPED(true); private final boolean mIsHardReason; @@ -361,6 +363,10 @@ public class DataEvaluation { * The normal reason. This is the most common case. */ NORMAL, + /** + * Data is allowed because an ongoing VoPS call depends on this network + */ + IN_VOICE_CALL, /** * The network brought up by this network request is unmetered. Should allowed no matter * the user enables or disables data. diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java index 4e165f7399103815c65646699a000d6bd36e60f1..d533933bf24d8fbb860d819f386e7d0facd1e356 100644 --- a/src/java/com/android/internal/telephony/data/DataNetwork.java +++ b/src/java/com/android/internal/telephony/data/DataNetwork.java @@ -16,6 +16,8 @@ package com.android.internal.telephony.data; +import static android.telephony.TelephonyManager.HAL_SERVICE_DATA; + import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; @@ -52,6 +54,7 @@ import android.telephony.Annotation.NetCapability; import android.telephony.Annotation.NetworkType; import android.telephony.Annotation.ValidationStatus; import android.telephony.AnomalyReporter; +import android.telephony.CarrierConfigManager; import android.telephony.DataFailCause; import android.telephony.DataSpecificRegistrationInfo; import android.telephony.LinkCapacityEstimate; @@ -923,8 +926,9 @@ public class DataNetwork extends StateMachine { mDataAllowedReason = dataAllowedReason; dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime()); mAttachedNetworkRequestList.addAll(networkRequestList); - mCid.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, INVALID_CID); - mCid.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, INVALID_CID); + for (int transportType : mAccessNetworksManager.getAvailableTransports()) { + mCid.put(transportType, INVALID_CID); + } mTelephonyDisplayInfo = mPhone.getDisplayInfoController().getTelephonyDisplayInfo(); mTcpBufferSizes = mDataConfigManager.getTcpConfigString(mTelephonyDisplayInfo); @@ -1336,13 +1340,14 @@ public class DataNetwork extends StateMachine { } } - // If we've ever received PCO data before connected, now it's the time to - // process it. + // If we've ever received PCO data before connected, now it's the time to process it. mPcoData.getOrDefault(mCid.get(mTransport), Collections.emptyMap()) .forEach((pcoId, pcoData) -> { onPcoDataChanged(pcoData); }); + mDataNetworkCallback.invokeFromExecutor( + () -> mDataNetworkCallback.onLinkStatusChanged(DataNetwork.this, mLinkStatus)); notifyPreciseDataConnectionState(); updateSuspendState(); } @@ -1594,6 +1599,9 @@ public class DataNetwork extends StateMachine { //************************************************************// if (mEverConnected) { + mLinkStatus = DataCallResponse.LINK_STATUS_INACTIVE; + mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback + .onLinkStatusChanged(DataNetwork.this, mLinkStatus)); mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback .onDisconnected(DataNetwork.this, mFailCause, mTearDownReason)); if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { @@ -2298,8 +2306,13 @@ public class DataNetwork extends StateMachine { if (mLinkStatus != response.getLinkStatus()) { mLinkStatus = response.getLinkStatus(); log("Link status updated to " + DataUtils.linkStatusToString(mLinkStatus)); - mDataNetworkCallback.invokeFromExecutor( - () -> mDataNetworkCallback.onLinkStatusChanged(DataNetwork.this, mLinkStatus)); + if (isConnected()) { + // If the data network is in a transition state, the link status will be notified + // upon entering connected or disconnected state. If the data network is already + // connected, send the updated link status from the updated data call response. + mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback + .onLinkStatusChanged(DataNetwork.this, mLinkStatus)); + } } // Set link addresses @@ -2572,9 +2585,9 @@ public class DataNetwork extends StateMachine { log("Remove network since deactivate request returned an error."); mFailCause = DataFailCause.RADIO_NOT_AVAILABLE; transitionTo(mDisconnectedState); - } else if (mPhone.getHalVersion().less(RIL.RADIO_HAL_VERSION_2_0)) { + } else if (mPhone.getHalVersion(HAL_SERVICE_DATA).less(RIL.RADIO_HAL_VERSION_2_0)) { log("Remove network on deactivate data response on old HAL " - + mPhone.getHalVersion()); + + mPhone.getHalVersion(HAL_SERVICE_DATA)); mFailCause = DataFailCause.LOST_CONNECTION; transitionTo(mDisconnectedState); } @@ -2609,16 +2622,17 @@ public class DataNetwork extends StateMachine { reason == TEAR_DOWN_REASON_AIRPLANE_MODE_ON ? DataService.REQUEST_REASON_SHUTDOWN : DataService.REQUEST_REASON_NORMAL, obtainMessage(EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE)); - mDataCallSessionStats.setDeactivateDataCallReason(DataService.REQUEST_REASON_NORMAL); + mDataCallSessionStats.setDeactivateDataCallReason(reason); mInvokedDataDeactivation = true; } /** - * @return {@code true} if this is an IMS network and tear down should be delayed until call - * ends on this data network. + * @return {@code true} if we shall delay tear down this network because an active voice call is + * relying on it and + * {@link CarrierConfigManager#KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL} is enabled. */ public boolean shouldDelayImsTearDownDueToInCall() { - return mDataConfigManager.isImsDelayTearDownEnabled() + return mDataConfigManager.isImsDelayTearDownUntilVoiceCallEndEnabled() && mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL) && mPhone.getImsPhone() != null diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java index fb06e14be3652658f7ec86b281e60f973324d79a..2ee1ffd3fb5b864e9b3e70d2e2a4381bc6741e9f 100644 --- a/src/java/com/android/internal/telephony/data/DataNetworkController.java +++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java @@ -561,10 +561,10 @@ public class DataNetworkController extends Handler { /** * Called when internet data network is connected. * - * @param dataProfiles The data profiles of the connected internet data network. It should - * be only one in most of the cases. + * @param internetNetworks The connected internet data network. It should be only one in + * most of the cases. */ - public void onInternetDataNetworkConnected(@NonNull List dataProfiles) {} + public void onInternetDataNetworkConnected(@NonNull List internetNetworks) {} /** * Called when data network is connected. @@ -801,13 +801,10 @@ public class DataNetworkController extends Handler { log("DataNetworkController created."); mAccessNetworksManager = phone.getAccessNetworksManager(); - mDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, - new DataServiceManager(mPhone, looper, AccessNetworkConstants.TRANSPORT_TYPE_WWAN)); - if (!mAccessNetworksManager.isInLegacyMode()) { - mDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, - new DataServiceManager(mPhone, looper, - AccessNetworkConstants.TRANSPORT_TYPE_WLAN)); + for (int transport : mAccessNetworksManager.getAvailableTransports()) { + mDataServiceManagers.put(transport, new DataServiceManager(mPhone, looper, transport)); } + mDataConfigManager = new DataConfigManager(mPhone, looper); // ========== Anomaly counters ========== @@ -913,24 +910,7 @@ public class DataNetworkController extends Handler { public void onDataNetworkHandoverRetryStopped( @NonNull DataNetwork dataNetwork) { Objects.requireNonNull(dataNetwork); - int preferredTransport = mAccessNetworksManager - .getPreferredTransportByNetworkCapability( - dataNetwork.getApnTypeNetworkCapability()); - if (dataNetwork.getTransport() == preferredTransport) { - log("onDataNetworkHandoverRetryStopped: " + dataNetwork + " is already " - + "on the preferred transport " - + AccessNetworkConstants.transportTypeToString( - preferredTransport)); - return; - } - if (dataNetwork.shouldDelayImsTearDownDueToInCall()) { - log("onDataNetworkHandoverRetryStopped: Delay IMS tear down until call " - + "ends. " + dataNetwork); - return; - } - - tearDownGracefully(dataNetwork, - DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED); + DataNetworkController.this.onDataNetworkHandoverRetryStopped(dataNetwork); } }); mImsManager = mPhone.getContext().getSystemService(ImsManager.class); @@ -1011,12 +991,10 @@ public class DataNetworkController extends Handler { mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) .registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED); - if (!mAccessNetworksManager.isInLegacyMode()) { - mPhone.getServiceStateTracker().registerForServiceStateChanged(this, - EVENT_SERVICE_STATE_CHANGED, null); - mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) - .registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED); - } + mPhone.getServiceStateTracker().registerForServiceStateChanged(this, + EVENT_SERVICE_STATE_CHANGED, null); + mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) + .registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED); mPhone.getContext().getSystemService(TelephonyRegistryManager.class) .addOnSubscriptionsChangedListener(new OnSubscriptionsChangedListener() { @@ -1640,13 +1618,11 @@ public class DataNetworkController extends Handler { reason.isConditionBased()); if (dataProfile == null) { evaluation.addDataDisallowedReason(DataDisallowedReason.NO_SUITABLE_DATA_PROFILE); - } else if (reason == DataEvaluationReason.NEW_REQUEST - && (mDataRetryManager.isAnySetupRetryScheduled(dataProfile, transport) - || mDataRetryManager.isSimilarNetworkRequestRetryScheduled( - networkRequest, transport))) { - // If this is a new request, check if there is any retry already scheduled. For all - // other evaluation reasons, since they are all condition changes, so if there is any - // retry scheduled, we still want to go ahead and setup the data network. + } else if (// Check for new requests if we already self-scheduled(as opposed to modem + // demanded) retry for similar requests. + reason == DataEvaluationReason.NEW_REQUEST + && mDataRetryManager.isSimilarNetworkRequestRetryScheduled( + networkRequest, transport)) { evaluation.addDataDisallowedReason(DataDisallowedReason.RETRY_SCHEDULED); } else if (mDataRetryManager.isDataProfileThrottled(dataProfile, transport)) { evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_THROTTLED); @@ -1771,15 +1747,20 @@ public class DataNetworkController extends Handler { } } - boolean isMmtel = false; - // If the data network is IMS that supports voice call, and has MMTEL request (client - // specified VoPS is required.) - if (dataNetwork.getAttachedNetworkRequestList().get( - new int[]{NetworkCapabilities.NET_CAPABILITY_MMTEL}) != null) { - // When reaching here, it means the network supports MMTEL, and also has MMTEL request - // attached to it. - isMmtel = true; - if (!dataNetwork.shouldDelayImsTearDownDueToInCall()) { + boolean vopsIsRequired = dataNetwork.hasNetworkCapabilityInNetworkRequests( + NetworkCapabilities.NET_CAPABILITY_MMTEL); + + // Check an active call relying on this network and config for "delay tear down due to vops + // call" is enabled. + if (dataNetwork.shouldDelayImsTearDownDueToInCall()) { + if (vopsIsRequired) { + log("Ignored VoPS check due to delay IMS tear down until call ends."); + } + } else { + // Reach here means we should ignore active calls even if there are any. + + // Check if VoPS requirement is met. + if (vopsIsRequired) { if (dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, @@ -1794,16 +1775,20 @@ public class DataNetworkController extends Handler { } } } - } else { - log("Ignored VoPS check due to delay IMS tear down until call ends."); + } + + // Check if handover retry stopped and preferred transport still not matched. + int preferredTransport = mAccessNetworksManager + .getPreferredTransportByNetworkCapability( + dataNetwork.getApnTypeNetworkCapability()); + if (preferredTransport != dataNetwork.getTransport() + && mDataRetryManager.isDataNetworkHandoverRetryStopped(dataNetwork)) { + evaluation.addDataDisallowedReason(DataDisallowedReason.HANDOVER_RETRY_STOPPED); } } // Check if data is disabled - boolean dataDisabled = false; - if (!mDataSettingsManager.isDataEnabled()) { - dataDisabled = true; - } + boolean dataDisabled = !mDataSettingsManager.isDataEnabled(); // Check if data roaming is disabled if (mServiceState.getDataRoaming() && !mDataSettingsManager.isDataRoamingEnabled()) { @@ -1818,17 +1803,14 @@ public class DataNetworkController extends Handler { DataProfile dataProfile = dataNetwork.getDataProfile(); if (dataProfile.getApnSetting() != null) { // Check if data is disabled for the APN type - dataDisabled = !mDataSettingsManager.isDataEnabled(DataUtils - .networkCapabilityToApnType(DataUtils - .getHighestPriorityNetworkCapabilityFromDataProfile( - mDataConfigManager, dataProfile))); + dataDisabled = !mDataSettingsManager.isDataEnabled( + DataUtils.networkCapabilityToApnType( + dataNetwork.getApnTypeNetworkCapability())); // Sometimes network temporarily OOS and network type becomes UNKNOWN. We don't // tear down network in that case. if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN - && !dataProfile.getApnSetting().canSupportLingeringNetworkType(networkType) - // delay IMS tear down if SRVCC in progress - && !(isMmtel && mIsSrvccHandoverInProcess)) { + && !dataProfile.getApnSetting().canSupportLingeringNetworkType(networkType)) { log("networkType=" + TelephonyManager.getNetworkTypeName(networkType) + ", networkTypeBitmask=" + TelephonyManager.convertNetworkTypeBitmaskToString( @@ -1855,7 +1837,8 @@ public class DataNetworkController extends Handler { // If users switch preferred profile in APN editor, we need to tear down network. if (dataNetwork.isInternetSupported() && !mDataProfileManager.isDataProfilePreferred(dataProfile) - && mDataProfileManager.isAnyPreferredDataProfileExisting()) { + && mDataProfileManager.canPreferredDataProfileSatisfy( + dataNetwork.getAttachedNetworkRequestList())) { evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_PROFILE_NOT_PREFERRED); } @@ -1891,6 +1874,15 @@ public class DataNetworkController extends Handler { } } + // Check if we allow additional lingering for active VoPS call network if + // a. this network is SRVCC handover in progress + // or b. "delay tear down due to active VoPS call" is enabled + boolean isInSrvcc = vopsIsRequired && mIsSrvccHandoverInProcess; + if (evaluation.containsOnly(DataDisallowedReason.DATA_NETWORK_TYPE_NOT_ALLOWED) + && (dataNetwork.shouldDelayImsTearDownDueToInCall() || isInSrvcc)) { + evaluation.addDataAllowedReason(DataAllowedReason.IN_VOICE_CALL); + } + log("Evaluated " + dataNetwork + ", " + evaluation); return evaluation; } @@ -2095,6 +2087,8 @@ public class DataNetworkController extends Handler { return DataNetwork.TEAR_DOWN_REASON_VOPS_NOT_SUPPORTED; case ONLY_ALLOWED_SINGLE_NETWORK: return DataNetwork.TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK; + case HANDOVER_RETRY_STOPPED: + return DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED; } } return DataNetwork.TEAR_DOWN_REASON_NONE; @@ -2813,6 +2807,32 @@ public class DataNetworkController extends Handler { tryHandoverDataNetwork(dataNetwork, preferredTransport, dataHandoverRetryEntry); } + /** + * Called when data network reached max handover retry count. + * + * @param dataNetwork The data network. + */ + private void onDataNetworkHandoverRetryStopped(@NonNull DataNetwork dataNetwork) { + int preferredTransport = mAccessNetworksManager + .getPreferredTransportByNetworkCapability( + dataNetwork.getApnTypeNetworkCapability()); + if (dataNetwork.getTransport() == preferredTransport) { + log("onDataNetworkHandoverRetryStopped: " + dataNetwork + " is already " + + "on the preferred transport " + + AccessNetworkConstants.transportTypeToString( + preferredTransport)); + return; + } + if (dataNetwork.shouldDelayImsTearDownDueToInCall()) { + log("onDataNetworkHandoverRetryStopped: Delay IMS tear down until call " + + "ends. " + dataNetwork); + return; + } + + tearDownGracefully(dataNetwork, + DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED); + } + /** * Called when data network validation status changed. * @@ -3165,6 +3185,16 @@ public class DataNetworkController extends Handler { logl("Start handover " + dataNetwork + " to " + AccessNetworkConstants.transportTypeToString(targetTransport)); dataNetwork.startHandover(targetTransport, dataHandoverRetryEntry); + } else if (dataEvaluation.containsOnly(DataDisallowedReason.NOT_IN_SERVICE) + && dataNetwork.shouldDelayImsTearDownDueToInCall()) { + // We try to preserve voice call in the case of temporary preferred transport mismatch + if (dataHandoverRetryEntry != null) { + dataHandoverRetryEntry.setState(DataRetryEntry.RETRY_STATE_FAILED); + } + mDataRetryManager.evaluateDataHandoverRetry(dataNetwork, + DataFailCause.HANDOVER_FAILED, + DataCallResponse.RETRY_DURATION_UNDEFINED /* retry mills */); + logl("tryHandoverDataNetwork: Scheduled retry due to in voice call and target OOS"); } else if (dataEvaluation.containsAny(DataDisallowedReason.NOT_ALLOWED_BY_POLICY, DataDisallowedReason.NOT_IN_SERVICE, DataDisallowedReason.VOPS_NOT_SUPPORTED)) { @@ -3469,9 +3499,7 @@ public class DataNetworkController extends Handler { && mInternetDataNetworkState == TelephonyManager.DATA_DISCONNECTED) { mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor( () -> callback.onInternetDataNetworkConnected( - allConnectedInternetDataNetworks.stream() - .map(DataNetwork::getDataProfile) - .collect(Collectors.toList())))); + allConnectedInternetDataNetworks))); } else if (dataNetworkState == TelephonyManager.DATA_DISCONNECTED && mInternetDataNetworkState == TelephonyManager.DATA_CONNECTED) { mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor( diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java index bcb9a36d2b4655b4ba8d2b34a5f7135b95b404f9..893ec4174b05cf065e190a5481f11451986ab160 100644 --- a/src/java/com/android/internal/telephony/data/DataProfileManager.java +++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java @@ -171,8 +171,8 @@ public class DataProfileManager extends Handler { new DataNetworkControllerCallback(this::post) { @Override public void onInternetDataNetworkConnected( - @NonNull List dataProfiles) { - DataProfileManager.this.onInternetDataNetworkConnected(dataProfiles); + @NonNull List internetNetworks) { + DataProfileManager.this.onInternetDataNetworkConnected(internetNetworks); } @Override @@ -413,24 +413,37 @@ public class DataProfileManager extends Handler { /** * Called when internet data is connected. * - * @param dataProfiles The connected internet data networks' profiles. + * @param internetNetworks The connected internet data networks. */ - private void onInternetDataNetworkConnected(@NonNull List dataProfiles) { - // Most of the cases there should be only one, but in case there are multiple, choose the - // one which has longest life cycle. - DataProfile dataProfile = dataProfiles.stream() - .max(Comparator.comparingLong(DataProfile::getLastSetupTimestamp).reversed()) - .orElse(null); + private void onInternetDataNetworkConnected(@NonNull List internetNetworks) { + DataProfile defaultProfile = null; + if (internetNetworks.size() == 1) { + // Most of the cases there should be only one. + defaultProfile = internetNetworks.get(0).getDataProfile(); + } else if (internetNetworks.size() > 1) { + // but in case there are multiple, find the default internet network, and choose the + // one which has longest life cycle. + logv("onInternetDataNetworkConnected: mPreferredDataProfile=" + mPreferredDataProfile + + " internetNetworks=" + internetNetworks); + defaultProfile = internetNetworks.stream() + .filter(network -> mPreferredDataProfile == null + || canPreferredDataProfileSatisfy( + network.getAttachedNetworkRequestList())) + .map(DataNetwork::getDataProfile) + .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp)) + .orElse(null); + } // Update a working internet data profile as a future candidate for preferred data profile // after APNs are reset to default - mLastInternetDataProfile = dataProfile; + mLastInternetDataProfile = defaultProfile; - // If there is no preferred data profile, then we should use one of the data profiles, - // which is good for internet, as the preferred data profile. - if (mPreferredDataProfile != null) return; + // If the live default internet network is not using the preferred data profile, since + // brought up a network means it passed sophisticated checks, update the preferred data + // profile so that this network won't be torn down in future network evaluations. + if (defaultProfile == null || defaultProfile.equals(mPreferredDataProfile)) return; // Save the preferred data profile into database. - setPreferredDataProfile(dataProfile); + setPreferredDataProfile(defaultProfile); updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED); } @@ -484,7 +497,7 @@ public class DataProfileManager extends Handler { * the preferred data profile from database. */ private void setPreferredDataProfile(@Nullable DataProfile dataProfile) { - log("setPreferredDataProfile: " + dataProfile); + logl("setPreferredDataProfile: " + dataProfile); String subId = Long.toString(mPhone.getSubId()); Uri uri = Uri.withAppendedPath(Telephony.Carriers.PREFERRED_APN_URI, subId); @@ -782,6 +795,18 @@ public class DataProfileManager extends Handler { return areDataProfilesSharingApn(dataProfile, mPreferredDataProfile); } + /** + * @param networkRequests The required network requests + * @return {@code true} if we currently have a preferred data profile that's capable of + * satisfying the required network requests; {@code false} if we have no preferred, or the + * preferred cannot satisfy the required requests. + */ + public boolean canPreferredDataProfileSatisfy( + @NonNull DataNetworkController.NetworkRequestList networkRequests) { + return mPreferredDataProfile != null && networkRequests.stream() + .allMatch(request -> request.canBeSatisfiedBy(mPreferredDataProfile)); + } + /** * Check if there is tethering data profile for certain network type. * @@ -803,18 +828,6 @@ public class DataProfileManager extends Handler { return getDataProfileForNetworkRequest(networkRequest, networkType, true) != null; } - /** - * Check if any preferred data profile exists. - * - * @return {@code true} if any preferred data profile exists - */ - public boolean isAnyPreferredDataProfileExisting() { - for (DataProfile dataProfile : mAllDataProfiles) { - if (dataProfile.isPreferred()) return true; - } - return false; - } - /** * Dedupe the similar data profiles. */ @@ -1130,6 +1143,7 @@ public class DataProfileManager extends Handler { pw.println("Preferred data profile from db=" + getPreferredDataProfileFromDb()); pw.println("Preferred data profile from config=" + getPreferredDataProfileFromConfig()); pw.println("Preferred data profile set id=" + mPreferredDataProfileSetId); + pw.println("Last internet data profile=" + mLastInternetDataProfile); pw.println("Initial attach data profile=" + mInitialAttachDataProfile); pw.println("isTetheringDataProfileExisting=" + isTetheringDataProfileExisting( TelephonyManager.NETWORK_TYPE_LTE)); diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java index ab653fee1cd3c658fed1003101ba85b43dc9fe26..b6ad10169339f0f8dfc91ead5b98c82ce3348b22 100644 --- a/src/java/com/android/internal/telephony/data/DataRetryManager.java +++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java @@ -21,6 +21,12 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.net.NetworkCapabilities; import android.os.AsyncResult; import android.os.Handler; @@ -72,6 +78,11 @@ import java.util.stream.Stream; public class DataRetryManager extends Handler { private static final boolean VDBG = false; + /** Intent of Alarm Manager for long retry timer. */ + private static final String ACTION_RETRY = "com.android.internal.telephony.data.ACTION_RETRY"; + /** The extra key for the hashcode of the retry entry for Alarm Manager. */ + private static final String ACTION_RETRY_EXTRA_HASHCODE = "extra_retry_hashcode"; + /** Event for data setup retry. */ private static final int EVENT_DATA_SETUP_RETRY = 3; @@ -98,6 +109,12 @@ public class DataRetryManager extends Handler { /** The maximum entries to preserve. */ private static final int MAXIMUM_HISTORICAL_ENTRIES = 100; + /** + * The threshold of retry timer, longer than or equal to which we use alarm manager to schedule + * instead of handler. + */ + private static final long RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS = TimeUnit + .MINUTES.toMillis(1); @IntDef(prefix = {"RESET_REASON_"}, value = { @@ -143,6 +160,9 @@ public class DataRetryManager extends Handler { /** Local log. */ private final @NonNull LocalLog mLocalLog = new LocalLog(128); + /** Alarm Manager used to schedule long set up or handover retries. */ + private final @NonNull AlarmManager mAlarmManager; + /** * The data retry callback. This is only used to notify {@link DataNetworkController} to retry * setup data network. @@ -952,16 +972,17 @@ public class DataRetryManager extends Handler { mDataServiceManagers = dataServiceManagers; mDataConfigManager = dataNetworkController.getDataConfigManager(); mDataProfileManager = dataNetworkController.getDataProfileManager(); + mAlarmManager = mPhone.getContext().getSystemService(AlarmManager.class); + mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) { @Override public void onCarrierConfigChanged() { DataRetryManager.this.onCarrierConfigUpdated(); } }); - mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) - .registerForApnUnthrottled(this, EVENT_DATA_PROFILE_UNTHROTTLED); - if (!mPhone.getAccessNetworksManager().isInLegacyMode()) { - mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) + + for (int transport : mPhone.getAccessNetworksManager().getAvailableTransports()) { + mDataServiceManagers.get(transport) .registerForApnUnthrottled(this, EVENT_DATA_PROFILE_UNTHROTTLED); } mDataProfileManager.registerCallback(new DataProfileManagerCallback(this::post) { @@ -997,6 +1018,19 @@ public class DataRetryManager extends Handler { mRil.registerForOn(this, EVENT_RADIO_ON, null); mRil.registerForModemReset(this, EVENT_MODEM_RESET, null); + // Register intent of alarm manager for long retry timer + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_RETRY); + mPhone.getContext().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_RETRY.equals(intent.getAction())) { + DataRetryManager.this.onAlarmIntentRetry( + intent.getIntExtra(ACTION_RETRY_EXTRA_HASHCODE, -1 /*Bad hashcode*/)); + } + } + }, intentFilter); + if (mDataConfigManager.shouldResetDataThrottlingWhenTacChanges()) { mPhone.getServiceStateTracker().registerForAreaCodeChanged(this, EVENT_TAC_CHANGED, null); @@ -1190,13 +1224,14 @@ public class DataRetryManager extends Handler { return; } - int failedCount = getRetryFailedCount(capability, retryRule); + int failedCount = getRetryFailedCount(capability, retryRule, transport); log("For capability " + DataUtils.networkCapabilityToString(capability) + ", found matching rule " + retryRule + ", failed count=" + failedCount); if (failedCount == retryRule.getMaxRetries()) { - log("Data retry failed for " + failedCount + " times. Stopped " - + "timer-based data retry for " + log("Data retry failed for " + failedCount + " times on " + + AccessNetworkConstants.transportTypeToString(transport) + + ". Stopped timer-based data retry for " + DataUtils.networkCapabilityToString(capability) + ". Condition-based retry will still happen when condition " + "changes."); @@ -1299,6 +1334,24 @@ public class DataRetryManager extends Handler { } } + /** + * @param dataNetwork The data network to check. + * @return {@code true} if the data network had failed the maximum number of attempts for + * handover according to any retry rules. + */ + public boolean isDataNetworkHandoverRetryStopped(@NonNull DataNetwork dataNetwork) { + // Matching the rule in configured order. + for (DataHandoverRetryRule retryRule : mDataHandoverRetryRuleList) { + int failedCount = getRetryFailedCount(dataNetwork, retryRule); + if (failedCount == retryRule.getMaxRetries()) { + log("Data handover retry failed for " + failedCount + " times. Stopped " + + "handover retry."); + return true; + } + } + return false; + } + /** Cancel all retries and throttling entries. */ private void onReset(@RetryResetReason int reason) { logl("Remove all retry and throttling entries, reason=" + resetReasonToString(reason)); @@ -1354,16 +1407,18 @@ public class DataRetryManager extends Handler { * * @param networkCapability The network capability to check. * @param dataRetryRule The data retry rule. + * @param transport The transport on which setup failure has occurred. * @return The failed count since last successful data setup. */ private int getRetryFailedCount(@NetCapability int networkCapability, - @NonNull DataSetupRetryRule dataRetryRule) { + @NonNull DataSetupRetryRule dataRetryRule, @TransportType int transport) { int count = 0; for (int i = mDataRetryEntries.size() - 1; i >= 0; i--) { if (mDataRetryEntries.get(i) instanceof DataSetupRetryEntry) { DataSetupRetryEntry entry = (DataSetupRetryEntry) mDataRetryEntries.get(i); // count towards the last succeeded data setup. - if (entry.setupRetryType == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS) { + if (entry.setupRetryType == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS + && entry.transport == transport) { if (entry.networkRequestList.isEmpty()) { String msg = "Invalid data retry entry detected"; logl(msg); @@ -1395,20 +1450,50 @@ public class DataRetryManager extends Handler { * @param dataRetryEntry The data retry entry. */ private void schedule(@NonNull DataRetryEntry dataRetryEntry) { - logl("Scheduled data retry: " + dataRetryEntry); + logl("Scheduled data retry " + dataRetryEntry + + " hashcode=" + dataRetryEntry.hashCode()); mDataRetryEntries.add(dataRetryEntry); if (mDataRetryEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) { // Discard the oldest retry entry. mDataRetryEntries.remove(0); } - // Using delayed message instead of alarm manager to schedule data retry is intentional. - // When the device enters doze mode, the handler message might be extremely delayed than the - // original scheduled time. There is no need to wake up the device to perform data retry in - // that case. - sendMessageDelayed(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry - ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry), - dataRetryEntry.retryDelayMillis); + // When the device is in doze mode, the handler message might be extremely delayed because + // handler uses relative system time(not counting sleep) which is inaccurate even when we + // enter the maintenance window. + // Therefore, we use alarm manager when we need to schedule long timers. + if (dataRetryEntry.retryDelayMillis <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) { + sendMessageDelayed(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry + ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry), + dataRetryEntry.retryDelayMillis); + } 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, + dataRetryEntry.retryElapsedTime, + PendingIntent.getBroadcast(mPhone.getContext(), + dataRetryEntry.hashCode() /*Unique identifier of this retry attempt*/, + intent, + PendingIntent.FLAG_IMMUTABLE)); + } + } + + /** + * Called when it's time to retry scheduled by Alarm Manager. + * @param retryHashcode The hashcode is the unique identifier of which retry entry to retry. + */ + private void onAlarmIntentRetry(int retryHashcode) { + DataRetryEntry dataRetryEntry = mDataRetryEntries.stream() + .filter(entry -> entry.hashCode() == retryHashcode) + .findAny() + .orElse(null); + logl("onAlarmIntentRetry: found " + dataRetryEntry + " with hashcode " + retryHashcode); + if (dataRetryEntry != null) { + sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry + ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry)); + } } /** @@ -1431,15 +1516,20 @@ public class DataRetryManager extends Handler { @TransportType int transport, @ElapsedRealtimeLong long expirationTime) { DataThrottlingEntry entry = new DataThrottlingEntry(dataProfile, networkRequestList, dataNetwork, transport, retryType, expirationTime); - if (mDataThrottlingEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) { - mDataThrottlingEntries.remove(0); - } - - // Remove previous entry that contains the same data profile. + // Remove previous entry that contains the same data profile. Therefore it should always + // contain at maximum all the distinct data profiles of the current subscription. mDataThrottlingEntries.removeIf( throttlingEntry -> dataProfile.equals(throttlingEntry.dataProfile)); - + if (mDataThrottlingEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) { + // If we don't see the anomaly report after U release, we should remove this check for + // the commented reason above. + AnomalyReporter.reportAnomaly( + UUID.fromString("24fd4d46-1d0f-4b13-b7d6-7bad70b8289b"), + "DataRetryManager throttling more than 100 data profiles", + mPhone.getCarrierId()); + mDataThrottlingEntries.remove(0); + } logl("Add throttling entry " + entry); mDataThrottlingEntries.add(entry); @@ -1474,11 +1564,11 @@ public class DataRetryManager extends Handler { * When this is set, {@code dataProfile} must be {@code null}. * @param transport The transport that this unthrottling request is on. * @param remove Whether to remove unthrottled entries from the list of entries. - * @param retry Whether schedule data setup retry after unthrottling. + * @param retry Whether schedule retry after unthrottling. */ private void onDataProfileUnthrottled(@Nullable DataProfile dataProfile, @Nullable String apn, @TransportType int transport, boolean remove, boolean retry) { - log("onDataProfileUnthrottled: data profile=" + dataProfile + ", apn=" + apn + log("onDataProfileUnthrottled: dataProfile=" + dataProfile + ", apn=" + apn + ", transport=" + AccessNetworkConstants.transportTypeToString(transport) + ", remove=" + remove); @@ -1490,11 +1580,9 @@ public class DataRetryManager extends Handler { // equal to the data profiles kept in data profile manager (due to some fields missing // in DataProfileInfo.aidl), so we need to get the equivalent data profile from data // profile manager. - log("onDataProfileUnthrottled: dataProfile=" + dataProfile); Stream stream = mDataThrottlingEntries.stream(); stream = stream.filter(entry -> entry.expirationTimeMillis > now); if (dataProfile.getApnSetting() != null) { - dataProfile.getApnSetting().setPermanentFailed(false); stream = stream .filter(entry -> entry.dataProfile.getApnSetting() != null) .filter(entry -> entry.dataProfile.getApnSetting().getApnName() @@ -1535,6 +1623,7 @@ public class DataRetryManager extends Handler { final int dataRetryType = retryType; if (unthrottledProfile != null && unthrottledProfile.getApnSetting() != null) { + unthrottledProfile.getApnSetting().setPermanentFailed(false); throttleStatusList.addAll(unthrottledProfile.getApnSetting().getApnTypes().stream() .map(apnType -> new ThrottleStatus.Builder() .setApnType(apnType) @@ -1618,12 +1707,14 @@ public class DataRetryManager extends Handler { */ public boolean isSimilarNetworkRequestRetryScheduled( @NonNull TelephonyNetworkRequest networkRequest, @TransportType int transport) { + long now = SystemClock.elapsedRealtime(); for (int i = mDataRetryEntries.size() - 1; i >= 0; i--) { if (mDataRetryEntries.get(i) instanceof DataSetupRetryEntry) { DataSetupRetryEntry entry = (DataSetupRetryEntry) mDataRetryEntries.get(i); if (entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED && entry.setupRetryType - == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS) { + == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS + && entry.retryElapsedTime > now) { if (entry.networkRequestList.isEmpty()) { String msg = "Invalid data retry entry detected"; logl(msg); @@ -1645,23 +1736,6 @@ public class DataRetryManager extends Handler { return false; } - /** - * Check if there is any data setup retry scheduled with specified data profile. - * - * @param dataProfile The data profile to retry. - * @param transport The transport that the request is on. - * @return {@code true} if there is retry scheduled for this data profile. - */ - public boolean isAnySetupRetryScheduled(@NonNull DataProfile dataProfile, - @TransportType int transport) { - return mDataRetryEntries.stream() - .filter(DataSetupRetryEntry.class::isInstance) - .map(DataSetupRetryEntry.class::cast) - .anyMatch(entry -> entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED - && dataProfile.equals(entry.dataProfile) - && entry.transport == transport); - } - /** * Check if a specific data profile is explicitly throttled by the network. * @@ -1697,13 +1771,11 @@ public class DataRetryManager extends Handler { && ((DataHandoverRetryEntry) entry).dataNetwork == dataNetwork && entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED) .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED)); - mDataThrottlingEntries.removeIf(entry -> entry.dataNetwork == dataNetwork); } /** * Check if there is any data handover retry scheduled. * - * * @param dataNetwork The network network to retry handover. * @return {@code true} if there is retry scheduled for this network capability. */ diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java index f6b33add5e41df2aef0822a0a550397c71eea588..5178ae48315e5fc7a8400570c1a1b22fe3f37dc3 100644 --- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java +++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java @@ -28,7 +28,6 @@ import android.preference.PreferenceManager; import android.provider.Settings; import android.sysprop.TelephonyProperties; import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; import android.telephony.TelephonyManager.MobileDataPolicy; @@ -46,8 +45,8 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.SettingsObserver; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback; +import com.android.internal.telephony.metrics.DeviceTelephonyPropertiesStats; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.util.TelephonyUtils; @@ -396,16 +395,10 @@ public class DataSettingsManager extends Handler { } private boolean isStandAloneOpportunistic(int subId) { - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(subId); - return subInfo != null && subInfo.isOpportunistic() - && TextUtils.isEmpty(subInfo.getGroupUuid()); - } - SubscriptionInfo info = SubscriptionController.getInstance().getActiveSubscriptionInfo( - subId, mPhone.getContext().getOpPackageName(), - mPhone.getContext().getAttributionTag()); - return (info != null) && info.isOpportunistic() && info.getGroupUuid() == null; + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(subId); + return subInfo != null && subInfo.isOpportunistic() + && TextUtils.isEmpty(subInfo.getGroupUuid()); } /** @@ -584,16 +577,11 @@ public class DataSettingsManager extends Handler { /** Refresh the enabled mobile data policies from Telephony database */ private void refreshEnabledMobileDataPolicy() { - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(mSubId); - if (subInfo != null) { - mEnabledMobileDataPolicy = getMobileDataPolicyEnabled( - subInfo.getEnabledMobileDataPolicies()); - } - } else { - mEnabledMobileDataPolicy = getMobileDataPolicyEnabled(SubscriptionController - .getInstance().getEnabledMobileDataPolicies(mSubId)); + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(mSubId); + if (subInfo != null) { + mEnabledMobileDataPolicy = getMobileDataPolicyEnabled( + subInfo.getEnabledMobileDataPolicies()); } } @@ -624,6 +612,8 @@ public class DataSettingsManager extends Handler { if (enable == isMobileDataPolicyEnabled(mobileDataPolicy)) { return; } + metricsRecordSetMobileDataPolicy(mobileDataPolicy); + if (enable) { mEnabledMobileDataPolicy.add(mobileDataPolicy); } else { @@ -632,23 +622,21 @@ public class DataSettingsManager extends Handler { String enabledMobileDataPolicies = mEnabledMobileDataPolicy.stream().map(String::valueOf) .collect(Collectors.joining(",")); - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().setEnabledMobileDataPolicies(mSubId, - enabledMobileDataPolicies); - logl(TelephonyUtils.mobileDataPolicyToString(mobileDataPolicy) + " changed to " - + enable); - updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE); - notifyDataEnabledOverrideChanged(enable, mobileDataPolicy); - } else { - if (SubscriptionController.getInstance().setEnabledMobileDataPolicies( - mSubId, enabledMobileDataPolicies)) { - logl(TelephonyUtils.mobileDataPolicyToString(mobileDataPolicy) + " changed to " - + enable); - updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE); - notifyDataEnabledOverrideChanged(enable, mobileDataPolicy); - } else { - loge("onSetMobileDataPolicy: failed to set " + enabledMobileDataPolicies); - } + SubscriptionManagerService.getInstance().setEnabledMobileDataPolicies(mSubId, + enabledMobileDataPolicies); + logl(TelephonyUtils.mobileDataPolicyToString(mobileDataPolicy) + " changed to " + + enable); + updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE); + notifyDataEnabledOverrideChanged(enable, mobileDataPolicy); + } + + /** + * Record the number of times a mobile data policy is toggled to metrics. + * @param mobileDataPolicy The mobile data policy that's toggled + */ + private void metricsRecordSetMobileDataPolicy(@MobileDataPolicy int mobileDataPolicy) { + if (mobileDataPolicy == TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH) { + DeviceTelephonyPropertiesStats.recordAutoDataSwitchFeatureToggle(); } } @@ -732,38 +720,25 @@ public class DataSettingsManager extends Handler { overridden = apnType == ApnSetting.TYPE_MMS; } - boolean isNonDds; - if (mPhone.isSubscriptionManagerServiceEnabled()) { - isNonDds = mPhone.getSubId() != SubscriptionManagerService.getInstance() - .getDefaultDataSubId(); - } else { - isNonDds = mPhone.getSubId() != SubscriptionController.getInstance() - .getDefaultDataSubId(); - } + boolean isNonDds = mPhone.getSubId() != SubscriptionManagerService.getInstance() + .getDefaultDataSubId(); // mobile data policy : data during call if (isMobileDataPolicyEnabled(TelephonyManager .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL)) { - overridden = isNonDds && mPhone.getState() != PhoneConstants.State.IDLE; + overridden = overridden || isNonDds && mPhone.getState() != PhoneConstants.State.IDLE; } // mobile data policy : auto data switch if (isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)) { - Phone defaultDataPhone; - if (mPhone.isSubscriptionManagerServiceEnabled()) { - // check user enabled data on the default data phone - defaultDataPhone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance() - .getPhoneId(SubscriptionManagerService.getInstance() - .getDefaultDataSubId())); - } else { - // check user enabled data on the default data phone - defaultDataPhone = PhoneFactory.getPhone(SubscriptionController.getInstance() - .getPhoneId(SubscriptionController.getInstance().getDefaultDataSubId())); - } + // check user enabled data on the default data phone + Phone defaultDataPhone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance() + .getPhoneId(SubscriptionManagerService.getInstance() + .getDefaultDataSubId())); if (defaultDataPhone == null) { loge("isDataEnabledOverriddenForApn: unexpected defaultDataPhone is null"); } else { - overridden = isNonDds && defaultDataPhone.isUserDataEnabled(); + overridden = overridden || isNonDds && defaultDataPhone.isUserDataEnabled(); } } return overridden; diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java index f5b60e1de27267a03cccf56055898e3d0030494a..6c6f06455d6ea161e857f947c49de3737d17b861 100644 --- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java +++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java @@ -16,6 +16,8 @@ package com.android.internal.telephony.data; +import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; + import android.annotation.CallbackExecutor; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; @@ -32,7 +34,6 @@ import android.telephony.Annotation.ValidationStatus; import android.telephony.CellSignalStrength; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.telephony.data.DataProfile; import android.util.IndentingPrintWriter; import android.util.LocalLog; @@ -267,7 +268,7 @@ public class DataStallRecoveryManager extends Handler { @Override public void onInternetDataNetworkConnected( - @NonNull List dataProfiles) { + @NonNull List internetNetworks) { mIsInternetNetworkConnected = true; logl("onInternetDataNetworkConnected"); } @@ -489,7 +490,7 @@ 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); - mPhone.getContext().sendBroadcast(intent); + mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE); } /** Recovery Action: RECOVERY_ACTION_GET_DATA_CALL_LIST */ @@ -640,7 +641,10 @@ public class DataStallRecoveryManager extends Handler { if (isLogNeeded) { timeDurationOfCurrentAction = - (isFirstDataStall == true ? 0 : (int) getDurationOfCurrentRecoveryMs()); + ((getRecoveryAction() > RECOVERY_ACTION_GET_DATA_CALL_LIST + && !mIsAttemptedAllSteps) + || mLastAction == RECOVERY_ACTION_RESET_MODEM) + ? (int) getDurationOfCurrentRecoveryMs() : 0; DataStallRecoveryStats.onDataStallEvent( mLastAction, mPhone, isValid, timeDuration, reason, isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction); diff --git a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java index 5ceb5e46e1d3faff2bba9bbb603a4743ac7f824e..5ed12aadecbf4a58dd60cbc9075e7f45e43086a9 100644 --- a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java +++ b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java @@ -458,6 +458,23 @@ public class LinkBandwidthEstimator extends Handler { long txBytesDelta = mobileTxBytes - mLastMobileTxBytes; long rxBytesDelta = mobileRxBytes - mLastMobileRxBytes; + int dataActivity; + if (txBytesDelta > 0 && rxBytesDelta > 0) { + dataActivity = TelephonyManager.DATA_ACTIVITY_INOUT; + } else if (rxBytesDelta > 0) { + dataActivity = TelephonyManager.DATA_ACTIVITY_IN; + } else if (txBytesDelta > 0) { + dataActivity = TelephonyManager.DATA_ACTIVITY_OUT; + } else { + dataActivity = TelephonyManager.DATA_ACTIVITY_NONE; + } + + if (mDataActivity != dataActivity) { + mDataActivity = dataActivity; + mLinkBandwidthEstimatorCallbacks.forEach(callback -> callback.invokeFromExecutor( + () -> callback.onDataActivityChanged(dataActivity))); + } + // Schedule the next traffic stats poll sendEmptyMessageDelayed(MSG_TRAFFIC_STATS_POLL, TRAFFIC_STATS_POLL_INTERVAL_MS); @@ -506,23 +523,6 @@ public class LinkBandwidthEstimator extends Handler { return; } - int dataActivity; - if (txBytesDelta > 0 && rxBytesDelta > 0) { - dataActivity = TelephonyManager.DATA_ACTIVITY_INOUT; - } else if (rxBytesDelta > 0) { - dataActivity = TelephonyManager.DATA_ACTIVITY_IN; - } else if (txBytesDelta > 0) { - dataActivity = TelephonyManager.DATA_ACTIVITY_OUT; - } else { - dataActivity = TelephonyManager.DATA_ACTIVITY_NONE; - } - - if (mDataActivity != dataActivity) { - mDataActivity = dataActivity; - mLinkBandwidthEstimatorCallbacks.forEach(callback -> callback.invokeFromExecutor( - () -> callback.onDataActivityChanged(dataActivity))); - } - long timeSinceLastFilterUpdateMs = currTimeMs - mFilterUpdateTimeMs; // Update filter if (timeSinceLastFilterUpdateMs >= FILTER_UPDATE_MAX_INTERVAL_MS) { @@ -678,7 +678,7 @@ public class LinkBandwidthEstimator extends Handler { return; } int linkBandwidthKbps = (int) linkBandwidthLongKbps; - mBwSampleValid = true; + mBwSampleValid = linkBandwidthKbps > 0; mBwSampleKbps = linkBandwidthKbps; String dataRatName = getDataRatName(mDataRat); diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java index 1ff7fde90b17c5bb0b788b53b7aae054558b3618..5e13f6b74e708ef0d4d03cd12daab4ffd81c84b1 100644 --- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java +++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java @@ -86,8 +86,6 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConfigurationManager; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.RadioConfig; -import com.android.internal.telephony.SubscriptionController; -import com.android.internal.telephony.SubscriptionController.WatchedInt; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList; import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback; @@ -97,6 +95,7 @@ import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwi import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDemandDataSwitch; 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; @@ -206,7 +205,6 @@ public class PhoneSwitcher extends Handler { private final @NonNull NetworkRequestList mNetworkRequestList = new NetworkRequestList(); protected final RegistrantList mActivePhoneRegistrants; - protected final SubscriptionController mSubscriptionController; private final SubscriptionManagerService mSubscriptionManagerService; protected final Context mContext; private final LocalLog mLocalLog; @@ -276,14 +274,8 @@ public class PhoneSwitcher extends Handler { protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX; // Subscription ID corresponds to mPreferredDataPhoneId. - protected WatchedInt mPreferredDataSubId = - new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - @Override - public void set(int newValue) { - super.set(newValue); - SubscriptionManager.invalidateActiveDataSubIdCaches(); - } - }; + protected WatchedInt mPreferredDataSubId = new WatchedInt( + SubscriptionManager.INVALID_SUBSCRIPTION_ID); // If non-null, An emergency call is about to be started, is ongoing, or has just ended and we // are overriding the DDS. @@ -292,18 +284,8 @@ public class PhoneSwitcher extends Handler { private ISetOpportunisticDataCallback mSetOpptSubCallback; - /** Data config manager callback for updating device config. **/ - private final DataConfigManager.DataConfigManagerCallback mDataConfigManagerCallback = - new DataConfigManager.DataConfigManagerCallback(this::post) { - @Override - public void onDeviceConfigChanged() { - log("onDeviceConfigChanged"); - PhoneSwitcher.this.updateConfig(); - } - }; - private static final int EVENT_PRIMARY_DATA_SUB_CHANGED = 101; - protected static final int EVENT_SUBSCRIPTION_CHANGED = 102; + protected static final int EVENT_SUBSCRIPTION_CHANGED = 102; private static final int EVENT_REQUEST_NETWORK = 103; private static final int EVENT_RELEASE_NETWORK = 104; // ECBM has started/ended. If we just ended an emergency call and mEmergencyOverride is not @@ -361,6 +343,12 @@ public class PhoneSwitcher extends Handler { private List> 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 @@ -371,8 +359,7 @@ public class PhoneSwitcher extends Handler { /** * The maximum number of retries when a validation for switching failed. */ - private int mAutoDataSwitchValidationMaxRetry = - DataConfigManager.DEFAULT_AUTO_DATA_SWITCH_MAX_RETRY; + private int mAutoDataSwitchValidationMaxRetry; /** Data settings manager callback. Key is the phone id. */ private final @NonNull Map mDataSettingsManagerCallbacks = @@ -464,7 +451,6 @@ public class PhoneSwitcher extends Handler { public static PhoneSwitcher make(int maxDataAttachModemCount, Context context, Looper looper) { if (sPhoneSwitcher == null) { sPhoneSwitcher = new PhoneSwitcher(maxDataAttachModemCount, context, looper); - SubscriptionManager.invalidateActiveDataSubIdCaches(); } return sPhoneSwitcher; @@ -531,13 +517,7 @@ public class PhoneSwitcher extends Handler { mMaxDataAttachModemCount = maxActivePhones; mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService = SubscriptionManagerService.getInstance(); - mSubscriptionController = null; - } else { - mSubscriptionController = SubscriptionController.getInstance(); - mSubscriptionManagerService = null; - } + mSubscriptionManagerService = SubscriptionManagerService.getInstance(); mRadioConfig = RadioConfig.getInstance(); mValidator = CellularNetworkValidator.getInstance(); @@ -583,6 +563,8 @@ 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( @@ -663,16 +645,9 @@ public class PhoneSwitcher extends Handler { return false; } - SubscriptionInfo info; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - info = mSubscriptionManagerService - .getActiveSubscriptionInfoForSimSlotIndex(slotIndex, - mContext.getOpPackageName(), mContext.getAttributionTag()); - } else { - info = mSubscriptionController - .getActiveSubscriptionInfoForSimSlotIndex(slotIndex, - mContext.getOpPackageName(), null); - } + SubscriptionInfo info = mSubscriptionManagerService + .getActiveSubscriptionInfoForSimSlotIndex(slotIndex, + mContext.getOpPackageName(), mContext.getAttributionTag()); boolean uiccAppsEnabled = info != null && info.areUiccApplicationsEnabled(); IccCard iccCard = PhoneFactory.getPhone(slotIndex).getIccCard(); @@ -840,6 +815,9 @@ public class PhoneSwitcher extends Handler { } case EVENT_MODEM_COMMAND_RETRY: { int phoneId = (int) msg.obj; + if (mActiveModemCount <= phoneId) { + break; + } if (isPhoneIdValidForRetry(phoneId)) { logl("EVENT_MODEM_COMMAND_RETRY: resend modem command on phone " + phoneId); sendRilCommands(phoneId); @@ -905,50 +883,40 @@ public class PhoneSwitcher extends Handler { break; } case EVENT_PROCESS_SIM_STATE_CHANGE: { - int slotIndex = (int) msg.arg1; - int simState = (int) msg.arg2; + int slotIndex = msg.arg1; + int simState = msg.arg2; if (!SubscriptionManager.isValidSlotIndex(slotIndex)) { logl("EVENT_PROCESS_SIM_STATE_CHANGE: skip processing due to invalid slotId: " + slotIndex); - } else if (mCurrentDdsSwitchFailure.get(slotIndex).contains( + } 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); + sendRilCommands(slotIndex); + } + // SIM loaded after subscriptions slot mapping are done. Evaluate for auto + // data switch. + sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH); } - - registerConfigChange(); break; } } } /** - * Register for device config change on the primary data phone. - */ - private void registerConfigChange() { - Phone phone = getPhoneBySubId(mPrimaryDataSubId); - if (phone != null) { - DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager(); - dataConfig.registerCallback(mDataConfigManagerCallback); - updateConfig(); - sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH); - } - } - - /** - * Update data config. + * 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 updateConfig() { - Phone phone = getPhoneBySubId(mPrimaryDataSubId); - if (phone != null) { - DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager(); - mAutoDataSwitchAvailabilityStabilityTimeThreshold = - dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold(); - mAutoDataSwitchValidationMaxRetry = - dataConfig.getAutoDataSwitchValidationMaxRetry(); - } + 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) { @@ -1129,16 +1097,9 @@ public class PhoneSwitcher extends Handler { // auto data switch feature is disabled from server if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return; // check is valid DSDS - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - if (!isActiveSubId(mPrimaryDataSubId) || mSubscriptionManagerService - .getActiveSubIdList(true).length <= 1) { - return; - } - } else { - if (!isActiveSubId(mPrimaryDataSubId) - || mSubscriptionController.getActiveSubIdList(true).length <= 1) { - return; - } + if (!isActiveSubId(mPrimaryDataSubId) || mSubscriptionManagerService + .getActiveSubIdList(true).length <= 1) { + return; } Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId); @@ -1158,7 +1119,7 @@ public class PhoneSwitcher extends Handler { int candidateSubId = getAutoSwitchTargetSubIdIfExists(); if (candidateSubId != INVALID_SUBSCRIPTION_ID) { - startAutoDataSwitchStabilityCheck(candidateSubId, true); + startAutoDataSwitchStabilityCheck(candidateSubId, mRequirePingTestBeforeDataSwitch); } else { cancelPendingAutoDataSwitch(); } @@ -1193,7 +1154,8 @@ public class PhoneSwitcher extends Handler { if (isInService(mPhoneStates[primaryPhoneId])) { // primary becomes available - startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, true); + startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, + mRequirePingTestBeforeDataSwitch); return; } @@ -1317,12 +1279,7 @@ public class PhoneSwitcher extends Handler { boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged; // Check if user setting of default non-opportunistic data sub is changed. - int primaryDataSubId; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - primaryDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); - } else { - primaryDataSubId = mSubscriptionController.getDefaultDataSubId(); - } + int primaryDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); if (primaryDataSubId != mPrimaryDataSubId) { sb.append(" mPrimaryDataSubId ").append(mPrimaryDataSubId).append("->") .append(primaryDataSubId); @@ -1425,7 +1382,7 @@ public class PhoneSwitcher extends Handler { } if (newActivePhones.size() < mMaxDataAttachModemCount - && newActivePhones.contains(mPreferredDataPhoneId) + && !newActivePhones.contains(mPreferredDataPhoneId) && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) { newActivePhones.add(mPreferredDataPhoneId); } @@ -1597,13 +1554,9 @@ public class PhoneSwitcher extends Handler { } private boolean isActiveSubId(int subId) { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(subId); - return subInfo != null && subInfo.isActive(); - } else { - return mSubscriptionController.isActiveSubId(subId); - } + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + return subInfo != null && subInfo.isActive(); } // This updates mPreferredDataPhoneId which decides which phone should handle default network @@ -1686,11 +1639,7 @@ public class PhoneSwitcher extends Handler { } private Phone getPhoneBySubId(int subId) { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - return findPhoneById(mSubscriptionManagerService.getPhoneId(subId)); - } else { - return findPhoneById(mSubscriptionController.getPhoneId(subId)); - } + return findPhoneById(mSubscriptionManagerService.getPhoneId(subId)); } private Phone findPhoneById(final int phoneId) { @@ -2067,8 +2016,8 @@ public class PhoneSwitcher extends Handler { * @param reason The switching reason. */ private void logDataSwitchEvent(int subId, int state, int reason) { - logl("Data switch event. subId=" + subId + ", state=" + switchStateToString(state) - + ", reason=" + switchReasonToString(reason)); + logl("Data switch state=" + switchStateToString(state) + " due to reason=" + + switchReasonToString(reason) + " on subId " + subId); DataSwitch dataSwitch = new DataSwitch(); dataSwitch.state = state; dataSwitch.reason = reason; @@ -2121,15 +2070,9 @@ public class PhoneSwitcher extends Handler { } pw.println("mPreferredDataPhoneId=" + mPreferredDataPhoneId); pw.println("mPreferredDataSubId=" + mPreferredDataSubId.get()); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - pw.println("DefaultDataSubId=" + mSubscriptionManagerService.getDefaultDataSubId()); - pw.println("DefaultDataPhoneId=" + mSubscriptionManagerService.getPhoneId( - mSubscriptionManagerService.getDefaultDataSubId())); - } else { - pw.println("DefaultDataSubId=" + mSubscriptionController.getDefaultDataSubId()); - pw.println("DefaultDataPhoneId=" + mSubscriptionController.getPhoneId( - mSubscriptionController.getDefaultDataSubId())); - } + pw.println("DefaultDataSubId=" + mSubscriptionManagerService.getDefaultDataSubId()); + pw.println("DefaultDataPhoneId=" + mSubscriptionManagerService.getPhoneId( + mSubscriptionManagerService.getDefaultDataSubId())); pw.println("mPrimaryDataSubId=" + mPrimaryDataSubId); pw.println("mAutoSelectedDataSubId=" + mAutoSelectedDataSubId); pw.println("mIsRegisteredForImsRadioTechChange=" + mIsRegisteredForImsRadioTechChange); @@ -2141,6 +2084,7 @@ public class PhoneSwitcher extends Handler { pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold=" + mAutoDataSwitchAvailabilityStabilityTimeThreshold); pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry); + pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeDataSwitch); pw.println("mLastSwitchPreferredDataReason=" + switchReasonToString(mLastSwitchPreferredDataReason)); pw.println("mDisplayedAutoSwitchNotification=" + mDisplayedAutoSwitchNotification); @@ -2215,12 +2159,8 @@ public class PhoneSwitcher extends Handler { + switchReasonToString(mLastSwitchPreferredDataReason)); return; } - SubscriptionInfo subInfo; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - subInfo = mSubscriptionManagerService.getSubscriptionInfo(mAutoSelectedDataSubId); - } else { - subInfo = mSubscriptionController.getSubscriptionInfo(mAutoSelectedDataSubId); - } + SubscriptionInfo subInfo = mSubscriptionManagerService + .getSubscriptionInfo(mAutoSelectedDataSubId); if (subInfo == null || subInfo.isOpportunistic()) { loge("displayAutoDataSwitchNotification: mAutoSelectedDataSubId=" + mAutoSelectedDataSubId + " unexpected subInfo " + subInfo); @@ -2259,14 +2199,8 @@ public class PhoneSwitcher extends Handler { } private boolean isPhoneIdValidForRetry(int phoneId) { - int ddsPhoneId; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - ddsPhoneId = mSubscriptionManagerService.getPhoneId( - mSubscriptionManagerService.getDefaultDataSubId()); - } else { - ddsPhoneId = mSubscriptionController.getPhoneId( - mSubscriptionController.getDefaultDataSubId()); - } + int ddsPhoneId = mSubscriptionManagerService.getPhoneId( + mSubscriptionManagerService.getDefaultDataSubId()); if (ddsPhoneId != INVALID_PHONE_INDEX && ddsPhoneId == phoneId) { return true; } else { diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..9a75b43aa4aa31e639e7492099da5b6546a469b4 --- /dev/null +++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.AsyncResult; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; +import android.telephony.Annotation.DisconnectCauses; +import android.telephony.DomainSelectionService; +import android.telephony.DomainSelectionService.EmergencyScanType; +import android.telephony.DomainSelector; +import android.telephony.EmergencyRegResult; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.TransportSelectorCallback; +import android.telephony.WwanSelectorCallback; +import android.util.LocalLog; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.util.TelephonyUtils; + +import java.io.PrintWriter; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + + +/** + * Manages the information of request and the callback binder. + */ +public class DomainSelectionConnection { + + private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE; + + protected static final int EVENT_EMERGENCY_NETWORK_SCAN_RESULT = 1; + protected static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 2; + + /** Callback to receive responses from DomainSelectionConnection. */ + public interface DomainSelectionConnectionCallback { + /** + * Notifies that selection has terminated because there is no decision that can be made + * or a timeout has occurred. The call should be terminated when this method is called. + * + * @param cause Indicates the reason. + */ + void onSelectionTerminated(@DisconnectCauses int cause); + } + + /** An internal class implementing {@link TransportSelectorCallback} interface. */ + private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback { + @Override + public void onCreated(@NonNull DomainSelector selector) { + mDomainSelector = selector; + DomainSelectionConnection.this.onCreated(); + } + + @Override + public void onWlanSelected(boolean useEmergencyPdn) { + DomainSelectionConnection.this.onWlanSelected(useEmergencyPdn); + } + + @Override + public @NonNull WwanSelectorCallback onWwanSelected() { + if (mWwanSelectorCallback == null) { + mWwanSelectorCallback = new WwanSelectorCallbackWrapper(); + } + DomainSelectionConnection.this.onWwanSelected(); + return mWwanSelectorCallback; + } + + @Override + public void onWwanSelected(final Consumer consumer) { + if (mWwanSelectorCallback == null) { + mWwanSelectorCallback = new WwanSelectorCallbackWrapper(); + } + if (mWwanSelectedExecutor == null) { + mWwanSelectedExecutor = Executors.newSingleThreadExecutor(); + } + mWwanSelectedExecutor.execute(() -> { + DomainSelectionConnection.this.onWwanSelected(); + consumer.accept(mWwanSelectorCallback); + }); + } + + @Override + public void onSelectionTerminated(int cause) { + DomainSelectionConnection.this.onSelectionTerminated(cause); + dispose(); + } + } + + /** An internal class implementing {@link WwanSelectorCallback} interface. */ + private final class WwanSelectorCallbackWrapper + implements WwanSelectorCallback, CancellationSignal.OnCancelListener { + @Override + public void onRequestEmergencyNetworkScan(@NonNull List preferredNetworks, + @EmergencyScanType int scanType, @NonNull CancellationSignal signal, + @NonNull Consumer consumer) { + if (signal != null) signal.setOnCancelListener(this); + mResultCallback = consumer; + initHandler(); + DomainSelectionConnection.this.onRequestEmergencyNetworkScan( + preferredNetworks.stream().mapToInt(Integer::intValue).toArray(), scanType); + } + + @Override + public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, + boolean useEmergencyPdn) { + DomainSelectionConnection.this.onDomainSelected(domain, useEmergencyPdn); + } + + @Override + public void onCancel() { + DomainSelectionConnection.this.onCancel(); + } + } + + protected final class DomainSelectionConnectionHandler extends Handler { + DomainSelectionConnectionHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + AsyncResult ar; + switch (msg.what) { + case EVENT_EMERGENCY_NETWORK_SCAN_RESULT: + mIsWaitingForScanResult = false; + if (mResultCallback == null) break; + ar = (AsyncResult) msg.obj; + EmergencyRegResult regResult = (EmergencyRegResult) ar.result; + if (DBG) logd("EVENT_EMERGENCY_NETWORK_SCAN_RESULT result=" + regResult); + CompletableFuture.runAsync( + () -> mResultCallback.accept(regResult), + mController.getDomainSelectionServiceExecutor()).join(); + break; + case EVENT_QUALIFIED_NETWORKS_CHANGED: + onQualifiedNetworksChanged(); + break; + default: + loge("handleMessage unexpected msg=" + msg.what); + break; + } + } + } + + protected String mTag = "DomainSelectionConnection"; + + private final LocalLog mLocalLog = new LocalLog(30); + private final @NonNull TransportSelectorCallback mTransportSelectorCallback; + + /** + * Controls the communication between {@link DomainSelectionConnection} and + * {@link DomainSelectionService}. + */ + private final @NonNull DomainSelectionController mController; + /** Indicates whether the requested service is for emergency services. */ + private final boolean mIsEmergency; + + /** Interface to receive the request to trigger emergency network scan and selected domain. */ + private @Nullable WwanSelectorCallback mWwanSelectorCallback; + /** Interface to return the result of emergency network scan. */ + private @Nullable Consumer mResultCallback; + /** Interface to the {@link DomainSelector} created for this service. */ + private @Nullable DomainSelector mDomainSelector; + + /** The slot requested this connection. */ + protected @NonNull Phone mPhone; + /** The requested domain selector type. */ + private @DomainSelectionService.SelectorType int mSelectorType; + + /** The attributes required to determine the domain. */ + private @Nullable DomainSelectionService.SelectionAttributes mSelectionAttributes; + + private @Nullable Looper mLooper; + protected @Nullable DomainSelectionConnectionHandler mHandler; + private boolean mRegisteredRegistrant; + private boolean mIsWaitingForScanResult; + + private @NonNull AndroidFuture mOnComplete; + + private @Nullable Executor mWwanSelectedExecutor; + + /** + * Creates an instance. + * + * @param phone For which this service is requested. + * @param selectorType Indicates the type of the requested service. + * @param isEmergency Indicates whether this request is for emergency service. + * @param controller The controller to communicate with the domain selection service. + */ + public DomainSelectionConnection(@NonNull Phone phone, + @DomainSelectionService.SelectorType int selectorType, boolean isEmergency, + @NonNull DomainSelectionController controller) { + mController = controller; + mPhone = phone; + mSelectorType = selectorType; + mIsEmergency = isEmergency; + + mTransportSelectorCallback = new TransportSelectorCallbackWrapper(); + mOnComplete = new AndroidFuture<>(); + } + + /** + * Returns the attributes required to determine the domain for a telephony service. + * + * @return The attributes required to determine the domain. + */ + public @Nullable DomainSelectionService.SelectionAttributes getSelectionAttributes() { + return mSelectionAttributes; + } + + /** + * Returns the interface for the callbacks. + * + * @return The {@link TransportSelectorCallback} interface. + */ + @VisibleForTesting + public @NonNull TransportSelectorCallback getTransportSelectorCallback() { + return mTransportSelectorCallback; + } + + /** + * Returns the {@link CompletableFuture} to receive the selected domain. + * + * @return The callback to receive response. + */ + public @NonNull CompletableFuture getCompletableFuture() { + return mOnComplete; + } + + /** + * Returs the {@link Phone} which requested this connection. + * + * @return The {@link Phone} instance. + */ + public @NonNull Phone getPhone() { + return mPhone; + } + + /** + * Requests the domain selection servic to select a domain. + * + * @param attr The attributes required to determine the domain. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr) { + mSelectionAttributes = attr; + mController.selectDomain(attr, getTransportSelectorCallback()); + } + + /** + * Notifies that {@link DomainSelector} instance has been created for the selection request. + */ + public void onCreated() { + // Can be overridden if required + } + + /** + * Notifies that WLAN transport has been selected. + */ + public void onWlanSelected() { + // Can be overridden. + } + + /** + * Notifies that WLAN transport has been selected. + * + * @param useEmergencyPdn Indicates whether Wi-Fi emergency services use emergency PDN or not. + */ + public void onWlanSelected(boolean useEmergencyPdn) { + // Can be overridden. + onWlanSelected(); + } + + /** + * Notifies that WWAN transport has been selected. + */ + public void onWwanSelected() { + // Can be overridden. + } + + /** + * Notifies that selection has terminated because there is no decision that can be made + * or a timeout has occurred. The call should be terminated when this method is called. + * + * @param cause Indicates the reason. + */ + public void onSelectionTerminated(@DisconnectCauses int cause) { + // Can be overridden. + } + + /** + * Requests the emergency network scan. + * + * @param preferredNetworks The ordered list of preferred networks to scan. + * @param scanType Indicates the scan preference, such as full service or limited service. + */ + public void onRequestEmergencyNetworkScan( + @NonNull @RadioAccessNetworkType int[] preferredNetworks, + @EmergencyScanType int scanType) { + // Can be overridden if required + if (!mRegisteredRegistrant) { + mPhone.registerForEmergencyNetworkScan(mHandler, + EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null); + mRegisteredRegistrant = true; + } + mIsWaitingForScanResult = true; + mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null); + } + + /** + * Notifies the domain selected. + * + * @param domain The selected domain. + */ + public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) { + // Can be overridden if required + CompletableFuture future = getCompletableFuture(); + future.complete(domain); + } + + /** + * Notifies the domain selected. + * + * @param domain The selected domain. + * @param useEmergencyPdn Indicates whether emergency services use emergency PDN or not. + */ + public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, + boolean useEmergencyPdn) { + // Can be overridden if required + onDomainSelected(domain); + } + + /** + * Notifies that the emergency network scan is canceled. + */ + public void onCancel() { + // Can be overridden if required + onCancel(false); + } + + private void onCancel(boolean resetScan) { + if (mIsWaitingForScanResult) { + mIsWaitingForScanResult = false; + mPhone.cancelEmergencyNetworkScan(resetScan, null); + } + } + + /** + * Cancels an ongoing selection operation. It is up to the {@link DomainSelectionService} + * to clean up all ongoing operations with the framework. + */ + public void cancelSelection() { + if (mDomainSelector == null) return; + mDomainSelector.cancelSelection(); + dispose(); + } + + /** + * Requests the domain selection service to reselect a domain. + * + * @param attr The attributes required to determine the domain. + * @return The callback to receive the response. + */ + public @NonNull CompletableFuture reselectDomain( + @NonNull DomainSelectionService.SelectionAttributes attr) { + mSelectionAttributes = attr; + if (mDomainSelector == null) return null; + mOnComplete = new AndroidFuture<>(); + mDomainSelector.reselectDomain(attr); + return mOnComplete; + } + + /** + * Finishes the selection procedure and cleans everything up. + */ + public void finishSelection() { + if (mDomainSelector == null) return; + mDomainSelector.finishSelection(); + dispose(); + } + + /** Indicates that the service connection has been removed. */ + public void onServiceDisconnected() { + // Can be overridden. + dispose(); + } + + private void dispose() { + if (mRegisteredRegistrant) { + mPhone.unregisterForEmergencyNetworkScan(mHandler); + mRegisteredRegistrant = false; + } + onCancel(true); + mController.removeConnection(this); + if (mLooper != null) mLooper.quitSafely(); + mLooper = null; + mHandler = null; + } + + protected void initHandler() { + if (mLooper == null) { + HandlerThread handlerThread = new HandlerThread(mTag); + handlerThread.start(); + mLooper = handlerThread.getLooper(); + } + if (mHandler == null) mHandler = new DomainSelectionConnectionHandler(mLooper); + } + + /** + * Notifies the change of qualified networks. + */ + protected void onQualifiedNetworksChanged() { + if (mIsEmergency + && (mSelectorType == DomainSelectionService.SELECTOR_TYPE_CALLING)) { + // DomainSelectionConnection for emergency calls shall override this. + throw new IllegalStateException("DomainSelectionConnection for emergency calls" + + " should override onQualifiedNetworksChanged()"); + } + } + + /** + * Dumps local log. + */ + public void dump(@NonNull PrintWriter printWriter) { + mLocalLog.dump(printWriter); + } + + protected void logd(String msg) { + Log.d(mTag, msg); + } + + protected void logi(String msg) { + Log.i(mTag, msg); + mLocalLog.log(msg); + } + + protected void loge(String msg) { + Log.e(mTag, msg); + mLocalLog.log(msg); + } +} diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java new file mode 100644 index 0000000000000000000000000000000000000000..52c9960076015098031f95a18322fc80d42b48ef --- /dev/null +++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_SMS; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.telephony.BarringInfo; +import android.telephony.DomainSelectionService; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.telephony.TransportSelectorCallback; +import android.util.LocalLog; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.util.TelephonyUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.concurrent.Executor; + +/** + * Manages the connection to {@link DomainSelectionService}. + */ +public class DomainSelectionController { + private static final String TAG = "DomainSelectionController"; + private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE; + + private static final int EVENT_SERVICE_STATE_CHANGED = 1; + private static final int EVENT_BARRING_INFO_CHANGED = 2; + + private final HandlerThread mHandlerThread = + new HandlerThread("DomainSelectionControllerHandler"); + + private final DomainSelectionService mDomainSelectionService; + private final Handler mHandler; + // Only added or removed, never accessed on purpose. + private final LocalLog mLocalLog = new LocalLog(30); + + protected final Object mLock = new Object(); + protected final Context mContext; + + protected final int[] mConnectionCounts; + private final ArrayList mConnections = new ArrayList<>(); + + private final class DomainSelectionControllerHandler extends Handler { + DomainSelectionControllerHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + AsyncResult ar; + switch (msg.what) { + case EVENT_SERVICE_STATE_CHANGED: + ar = (AsyncResult) msg.obj; + updateServiceState((Phone) ar.userObj, (ServiceState) ar.result); + break; + case EVENT_BARRING_INFO_CHANGED: + ar = (AsyncResult) msg.obj; + updateBarringInfo((Phone) ar.userObj, (BarringInfo) ar.result); + break; + default: + loge("unexpected event=" + msg.what); + break; + } + } + } + + /** + * Creates an instance. + * + * @param context Context object from hosting application. + * @param service The {@link DomainSelectionService} instance. + */ + public DomainSelectionController(@NonNull Context context, + @NonNull DomainSelectionService service) { + this(context, service, null); + } + + /** + * Creates an instance. + * + * @param context Context object from hosting application. + * @param service The {@link DomainSelectionService} instance. + * @param looper Handles event messages. + */ + @VisibleForTesting + public DomainSelectionController(@NonNull Context context, + @NonNull DomainSelectionService service, @Nullable Looper looper) { + mContext = context; + mDomainSelectionService = service; + + if (looper == null) { + mHandlerThread.start(); + looper = mHandlerThread.getLooper(); + } + mHandler = new DomainSelectionControllerHandler(looper); + + int numPhones = TelephonyManager.getDefault().getActiveModemCount(); + mConnectionCounts = new int[numPhones]; + for (int i = 0; i < numPhones; i++) { + mConnectionCounts[i] = 0; + } + } + + /** + * Returns a {@link DomainSelectionConnection} instance. + * + * @param phone Indicates who requests the service. + * @param selectorType Indicates the selector type requested. + * @param isEmergency Indicates whether this is for emergency service. + * @return A {@link DomainSelectiionConnection} instance for the requested service. + * Returns {@code null} if the requested service is not supported. + */ + public @Nullable DomainSelectionConnection getDomainSelectionConnection( + @NonNull Phone phone, + @DomainSelectionService.SelectorType int selectorType, + boolean isEmergency) { + DomainSelectionConnection c = null; + + if (selectorType == SELECTOR_TYPE_CALLING) { + if (isEmergency) { + c = new EmergencyCallDomainSelectionConnection(phone, this); + } else { + c = new NormalCallDomainSelectionConnection(phone, this); + } + } else if (selectorType == SELECTOR_TYPE_SMS) { + if (isEmergency) { + c = new EmergencySmsDomainSelectionConnection(phone, this); + } else { + c = new SmsDomainSelectionConnection(phone, this); + } + } + + addConnection(c); + return c; + } + + private void addConnection(@Nullable DomainSelectionConnection c) { + if (c == null) return; + mConnections.add(c); + registerForStateChange(c); + } + + /** + * Releases resources for this connection. + */ + public void removeConnection(@Nullable DomainSelectionConnection c) { + if (c == null) return; + mConnections.remove(c); + unregisterForStateChange(c); + } + + /** + * Requests the domain selection. + * + * @param attr Attributetes required to determine the domain. + * @param callback A callback to receive the response. + */ + public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr, + @NonNull TransportSelectorCallback callback) { + if (attr == null || callback == null) return; + if (DBG) logd("selectDomain"); + + Executor e = mDomainSelectionService.getCachedExecutor(); + e.execute(() -> mDomainSelectionService.onDomainSelection(attr, callback)); + } + + /** + * Notifies the change in {@link ServiceState} for a specific slot. + * + * @param phone {@link Phone} which the state changed. + * @param serviceState Updated {@link ServiceState}. + */ + private void updateServiceState(Phone phone, ServiceState serviceState) { + if (phone == null || serviceState == null) return; + if (DBG) logd("updateServiceState phoneId=" + phone.getPhoneId()); + + Executor e = mDomainSelectionService.getCachedExecutor(); + e.execute(() -> mDomainSelectionService.onServiceStateUpdated( + phone.getPhoneId(), phone.getSubId(), serviceState)); + } + + /** + * Notifies the change in {@link BarringInfo} for a specific slot. + * + * @param phone {@link Phone} which the state changed. + * @param info Updated {@link BarringInfo}. + */ + private void updateBarringInfo(Phone phone, BarringInfo info) { + if (phone == null || info == null) return; + if (DBG) logd("updateBarringInfo phoneId=" + phone.getPhoneId()); + + Executor e = mDomainSelectionService.getCachedExecutor(); + e.execute(() -> mDomainSelectionService.onBarringInfoUpdated( + phone.getPhoneId(), phone.getSubId(), info)); + } + + /** + * Registers for the notification of {@link ServiceState} and {@link BarringInfo}. + * + * @param c {@link DomainSelectionConnection} for which the registration is requested. + */ + private void registerForStateChange(DomainSelectionConnection c) { + Phone phone = c.getPhone(); + int count = mConnectionCounts[phone.getPhoneId()]; + if (count < 0) count = 0; + + mConnectionCounts[phone.getPhoneId()] = count + 1; + if (count > 0) return; + + phone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, phone); + phone.mCi.registerForBarringInfoChanged(mHandler, EVENT_BARRING_INFO_CHANGED, phone); + + updateServiceState(phone, phone.getServiceStateTracker().getServiceState()); + updateBarringInfo(phone, phone.mCi.getLastBarringInfo()); + } + + /** + * Unregisters for the notification of {@link ServiceState} and {@link BarringInfo}. + * + * @param c {@link DomainSelectionConnection} for which the unregistration is requested. + */ + private void unregisterForStateChange(DomainSelectionConnection c) { + Phone phone = c.getPhone(); + int count = mConnectionCounts[phone.getPhoneId()]; + if (count < 1) count = 1; + + mConnectionCounts[phone.getPhoneId()] = count - 1; + if (count > 1) return; + + phone.unregisterForServiceStateChanged(mHandler); + phone.mCi.unregisterForBarringInfoChanged(mHandler); + } + + /** + * Notifies the {@link DomainSelectionConnection} instances registered + * of the service disconnection. + */ + private void notifyServiceDisconnected() { + for (DomainSelectionConnection c : mConnections) { + c.onServiceDisconnected(); + } + } + + /** + * Gets the {@link Executor} which executes methods of {@link DomainSelectionService.} + * @return {@link Executor} instance. + */ + public @NonNull Executor getDomainSelectionServiceExecutor() { + return mDomainSelectionService.getCachedExecutor(); + } + + /** + * Dumps logcal log + */ + public void dump(@NonNull PrintWriter printWriter) { + mLocalLog.dump(printWriter); + } + + private void logd(String msg) { + Log.d(TAG, msg); + } + + private void logi(String msg) { + Log.i(TAG, msg); + mLocalLog.log(msg); + } + + private void loge(String msg) { + Log.e(TAG, msg); + mLocalLog.log(msg); + } +} diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..cbb74fadd75f6b8fce8e68b819dad7bfd2e0dc7c --- /dev/null +++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; + +import static com.android.internal.telephony.RIL.RADIO_HAL_VERSION_2_1; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.telephony.DomainSelectionService; +import android.util.IndentingPrintWriter; +import android.util.LocalLog; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * This class is an entry point to provide whether the AOSP domain selection is supported or not, + * and bind the {@link DomainSelectionController} with the given {@link DomainSelectionService} to + * provide a specific {@link DomainSelectionConnection} object for communicating with each domain + * selector. + */ +public class DomainSelectionResolver { + private static final String TAG = DomainSelectionResolver.class.getSimpleName(); + private static DomainSelectionResolver sInstance = null; + + /** + * Creates the DomainSelectionResolver singleton instance. + * + * @param context The context of the application. + * @param deviceConfigEnabled The flag to indicate whether or not the device supports + * the domain selection service or not. + */ + public static void make(Context context, boolean deviceConfigEnabled) { + if (sInstance == null) { + sInstance = new DomainSelectionResolver(context, deviceConfigEnabled); + } + } + + /** + * Returns the singleton instance of DomainSelectionResolver. + * + * @return A {@link DomainSelectionResolver} instance. + */ + public static DomainSelectionResolver getInstance() { + if (sInstance == null) { + throw new IllegalStateException("DomainSelectionResolver is not ready!"); + } + return sInstance; + } + + /** + * Sets a {@link DomainSelectionResolver} for injecting mock DomainSelectionResolver. + * + * @param resolver A {@link DomainSelectionResolver} instance to test. + */ + @VisibleForTesting + public static void setDomainSelectionResolver(DomainSelectionResolver resolver) { + sInstance = resolver; + } + + /** + * Testing interface for injecting mock DomainSelectionController. + */ + @VisibleForTesting + public interface DomainSelectionControllerFactory { + /** + * Returns a {@link DomainSelectionController} created using the specified + * context and {@link DomainSelectionService} instance. + */ + DomainSelectionController create(@NonNull Context context, + @NonNull DomainSelectionService service); + } + + private DomainSelectionControllerFactory mDomainSelectionControllerFactory = + new DomainSelectionControllerFactory() { + @Override + public DomainSelectionController create(@NonNull Context context, + @NonNull DomainSelectionService service) { + return new DomainSelectionController(context, service); + } + }; + + // Persistent Logging + private final LocalLog mEventLog = new LocalLog(10); + private final Context mContext; + // The flag to indicate whether the device supports the domain selection service or not. + private final boolean mDeviceConfigEnabled; + // DomainSelectionController, which are bound to DomainSelectionService. + private DomainSelectionController mController; + + public DomainSelectionResolver(Context context, boolean deviceConfigEnabled) { + mContext = context; + mDeviceConfigEnabled = deviceConfigEnabled; + logi("DomainSelectionResolver created: device-config=" + deviceConfigEnabled); + } + + /** + * Checks if the device supports the domain selection service to route the call / SMS / + * supplementary services to the appropriate domain. + * This checks the device-config and Radio HAL version for supporting the domain selection. + * The domain selection requires the Radio HAL version greater than or equal to 2.1. + * + * @return {@code true} if the domain selection is supported on the device, + * {@code false} otherwise. + */ + public boolean isDomainSelectionSupported() { + return mDeviceConfigEnabled && PhoneFactory.getDefaultPhone() + .getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1); + } + + /** + * Returns a {@link DomainSelectionConnection} instance. + * + * @param phone The Phone instance for witch this request is. + * @param selectorType Indicates the selector type requested. + * @param isEmergency Indicates whether this is for emergency service. + * @throws IllegalStateException If the {@link DomainSelectionController} is not created + * because {@link #initialize} method is not called even if the domain selection is + * supported. + * @return A {@link DomainSelectionConnection} instance if the device supports + * AOSP domain selection and IMS is available or {@code null} otherwise. + */ + public @Nullable DomainSelectionConnection getDomainSelectionConnection(Phone phone, + @DomainSelectionService.SelectorType int selectorType, boolean isEmergency) { + if (mController == null) { + // If the caller calls this method without checking whether the domain selection + // is supported or not, this exception will be thrown. + throw new IllegalStateException("DomainSelection is not supported!"); + } + + if (phone == null || !phone.isImsAvailable()) { + // If ImsPhone is null or the binder of ImsService is not available, + // CS domain is used for the telephony services. + return null; + } + + return mController.getDomainSelectionConnection(phone, selectorType, isEmergency); + } + + /** Sets a factory interface for creating {@link DomainSelectionController} instance. */ + @VisibleForTesting + public void setDomainSelectionControllerFactory(DomainSelectionControllerFactory factory) { + mDomainSelectionControllerFactory = factory; + } + + /** + * Needs to be called after the constructor to create a {@link DomainSelectionController} that + * is bound to the given {@link DomainSelectionService}. + * + * @param service A {@link DomainSelectionService} to be bound. + */ + public void initialize(@NonNull DomainSelectionService service) { + logi("Initialize."); + mController = mDomainSelectionControllerFactory.create(mContext, service); + } + + /** + * Dumps this instance into a readable format for dumpsys usage. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println("Resolver:"); + ipw.increaseIndent(); + ipw.println("Event Log:"); + ipw.increaseIndent(); + mEventLog.dump(ipw); + ipw.decreaseIndent(); + ipw.decreaseIndent(); + + ipw.println("Controller:"); + ipw.increaseIndent(); + DomainSelectionController controller = mController; + if (controller == null) { + ipw.println("no active controller"); + } else { + controller.dump(ipw); + } + ipw.decreaseIndent(); + } + + private void logi(String s) { + Log.i(TAG, s); + mEventLog.log(s); + } +} diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..5f3c3b69e9a6a2506d6ac85165abb5599ffe937a --- /dev/null +++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_INVALID; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; + +import static com.android.internal.telephony.PhoneConstants.DOMAIN_NON_3GPP_PS; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.telephony.AccessNetworkConstants.TransportType; +import android.telephony.Annotation.DisconnectCauses; +import android.telephony.Annotation.NetCapability; +import android.telephony.DomainSelectionService; +import android.telephony.EmergencyRegResult; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.data.ApnSetting; +import android.telephony.ims.ImsReasonInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.data.AccessNetworksManager; +import com.android.internal.telephony.emergency.EmergencyStateTracker; + +import java.util.concurrent.CompletableFuture; + +/** + * Manages the information of request and the callback binder for emergency calling. + */ +public class EmergencyCallDomainSelectionConnection extends DomainSelectionConnection { + + private static final boolean DBG = false; + + private @NonNull EmergencyStateTracker mEmergencyStateTracker = null; + private @Nullable DomainSelectionConnectionCallback mCallback; + private @TransportType int mPreferredTransportType = TRANSPORT_TYPE_INVALID; + + /** + * Create an instance. + * + * @param phone For which this service is requested. + * @param controller The controller to communicate with the domain selection service. + */ + public EmergencyCallDomainSelectionConnection(@NonNull Phone phone, + @NonNull DomainSelectionController controller) { + this(phone, controller, EmergencyStateTracker.getInstance()); + } + + /** + * Create an instance. + * + * @param phone For which this service is requested. + * @param controller The controller to communicate with the domain selection service. + * @param tracker The {@link EmergencyStateTracker} instance. + */ + @VisibleForTesting + public EmergencyCallDomainSelectionConnection(@NonNull Phone phone, + @NonNull DomainSelectionController controller, @NonNull EmergencyStateTracker tracker) { + super(phone, SELECTOR_TYPE_CALLING, true, controller); + mTag = "EmergencyCallDomainSelectionConnection"; + + mEmergencyStateTracker = tracker; + } + + /** {@inheritDoc} */ + @Override + public void onWlanSelected(boolean useEmergencyPdn) { + mEmergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN); + if (useEmergencyPdn) { + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + int transportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY); + logi("onWlanSelected curTransportType=" + transportType); + if (transportType != TRANSPORT_TYPE_WLAN) { + changePreferredTransport(TRANSPORT_TYPE_WLAN); + return; + } + } + + CompletableFuture future = getCompletableFuture(); + if (future != null) future.complete(DOMAIN_NON_3GPP_PS); + } + + /** {@inheritDoc} */ + @Override + public void onWwanSelected() { + mEmergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN); + } + + /** {@inheritDoc} */ + @Override + public void onSelectionTerminated(@DisconnectCauses int cause) { + if (mCallback != null) mCallback.onSelectionTerminated(cause); + } + + /** {@inheritDoc} */ + @Override + public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, + boolean useEmergencyPdn) { + if (domain == DOMAIN_PS && useEmergencyPdn) { + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + int transportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY); + logi("onDomainSelected curTransportType=" + transportType); + if (transportType != TRANSPORT_TYPE_WWAN) { + changePreferredTransport(TRANSPORT_TYPE_WWAN); + return; + } + } + super.onDomainSelected(domain, useEmergencyPdn); + } + + /** + * Request a domain for emergency call. + * + * @param attr The attributes required to determine the domain. + * @param callback A callback to receive the response. + * @return the callback to receive the response. + */ + public @NonNull CompletableFuture createEmergencyConnection( + @NonNull DomainSelectionService.SelectionAttributes attr, + @NonNull DomainSelectionConnectionCallback callback) { + mCallback = callback; + selectDomain(attr); + return getCompletableFuture(); + } + + private void changePreferredTransport(@TransportType int transportType) { + logi("changePreferredTransport " + transportType); + initHandler(); + mPreferredTransportType = transportType; + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + anm.registerForQualifiedNetworksChanged(mHandler, EVENT_QUALIFIED_NETWORKS_CHANGED); + mPhone.notifyEmergencyDomainSelected(transportType); + } + + private AccessNetworksManager.AccessNetworksManagerCallback mPreferredTransportCallback = + new AccessNetworksManager.AccessNetworksManagerCallback(Runnable::run) { + @Override + public void onPreferredTransportChanged(@NetCapability int capability) { + } + }; + + /** {@inheritDoc} */ + @Override + protected void onQualifiedNetworksChanged() { + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + int preferredTransport = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY); + logi("onQualifiedNetworksChanged preferred=" + mPreferredTransportType + + ", current=" + preferredTransport); + if (preferredTransport == mPreferredTransportType) { + CompletableFuture future = getCompletableFuture(); + if (future != null) { + if (preferredTransport == TRANSPORT_TYPE_WLAN) { + future.complete(DOMAIN_NON_3GPP_PS); + } else { + future.complete(DOMAIN_PS); + } + } + anm.unregisterForQualifiedNetworksChanged(mHandler); + } + } + + /** {@inheritDoc} */ + @Override + public void cancelSelection() { + logi("cancelSelection"); + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + anm.unregisterForQualifiedNetworksChanged(mHandler); + super.cancelSelection(); + } + + /** + * Returns the attributes required to determine the domain for a telephony service. + * + * @param slotId The slot identifier. + * @param subId The subscription identifier. + * @param exited {@code true} if the request caused the device to move out of airplane mode. + * @param callId The call identifier. + * @param number The dialed number. + * @param callFailCause The reason why the last CS attempt failed. + * @param imsReasonInfo The reason why the last PS attempt failed. + * @param emergencyRegResult The current registration result for emergency services. + * @return The attributes required to determine the domain. + */ + public static @NonNull DomainSelectionService.SelectionAttributes getSelectionAttributes( + int slotId, int subId, boolean exited, + @NonNull String callId, @NonNull String number, int callFailCause, + @Nullable ImsReasonInfo imsReasonInfo, + @Nullable EmergencyRegResult emergencyRegResult) { + DomainSelectionService.SelectionAttributes.Builder builder = + new DomainSelectionService.SelectionAttributes.Builder( + slotId, subId, SELECTOR_TYPE_CALLING) + .setEmergency(true) + .setExitedFromAirplaneMode(exited) + .setCallId(callId) + .setNumber(number) + .setCsDisconnectCause(callFailCause); + + if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo); + if (emergencyRegResult != null) builder.setEmergencyRegResult(emergencyRegResult); + + return builder.build(); + } +} diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..efcdf116e4c32a28a53a66dfd099c7e5788d6912 --- /dev/null +++ b/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN; + +import android.annotation.NonNull; +import android.telephony.AccessNetworkConstants; +import android.telephony.AccessNetworkConstants.TransportType; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.data.ApnSetting; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.data.AccessNetworksManager; +import com.android.internal.telephony.emergency.EmergencyStateTracker; + +/** + * Manages the information of request and the callback binder for an emergency SMS. + */ +public class EmergencySmsDomainSelectionConnection extends SmsDomainSelectionConnection { + private final Object mLock = new Object(); + private @NonNull EmergencyStateTracker mEmergencyStateTracker; + private @TransportType int mPreferredTransportType = + AccessNetworkConstants.TRANSPORT_TYPE_INVALID; + + public EmergencySmsDomainSelectionConnection( + Phone phone, DomainSelectionController controller) { + this(phone, controller, EmergencyStateTracker.getInstance()); + } + + @VisibleForTesting + public EmergencySmsDomainSelectionConnection(Phone phone, + DomainSelectionController controller, EmergencyStateTracker tracker) { + super(phone, controller, true); + mTag = "DomainSelectionConnection-EmergencySMS"; + mEmergencyStateTracker = tracker; + } + + /** + * Notifies that WLAN transport has been selected. + * + * @param useEmergencyPdn A flag specifying whether Wi-Fi emergency service uses emergency PDN + * or not. + */ + @Override + public void onWlanSelected(boolean useEmergencyPdn) { + synchronized (mLock) { + if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) { + logi("Domain selection completion is in progress"); + return; + } + + mEmergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WLAN); + + if (useEmergencyPdn) { + // Change the transport type if the current preferred transport type for + // an emergency is not {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}. + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + if (anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY) + != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { + changePreferredTransport(AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + // The {@link #onDomainSlected()} will be called after the preferred transport + // is successfully changed and notified from the {@link AccessNetworksManager}. + return; + } + } + + super.onWlanSelected(useEmergencyPdn); + } + } + + @Override + public void onWwanSelected() { + mEmergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN); + } + + /** + * Notifies the domain selected. + * + * @param domain The selected domain. + * @param useEmergencyPdn A flag specifying whether emergency service uses emergency PDN or not. + */ + @Override + public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, + boolean useEmergencyPdn) { + synchronized (mLock) { + if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) { + logi("Domain selection completion is in progress"); + return; + } + + if (useEmergencyPdn && domain == NetworkRegistrationInfo.DOMAIN_PS) { + // Change the transport type if the current preferred transport type for + // an emergency is not {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN}. + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + if (anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY) + != AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { + changePreferredTransport(AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + // The {@link #onDomainSlected()} will be called after the preferred transport + // is successfully changed and notified from the {@link AccessNetworksManager}. + return; + } + } + + super.onDomainSelected(domain, useEmergencyPdn); + } + } + + @Override + public void finishSelection() { + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + + synchronized (mLock) { + if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) { + mPreferredTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; + anm.unregisterForQualifiedNetworksChanged(mHandler); + } + } + + super.finishSelection(); + } + + @Override + protected void onQualifiedNetworksChanged() { + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + int preferredTransportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY); + + synchronized (mLock) { + if (preferredTransportType == mPreferredTransportType) { + mPreferredTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; + super.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true); + anm.unregisterForQualifiedNetworksChanged(mHandler); + } + } + } + + private void changePreferredTransport(@TransportType int transportType) { + logi("Change preferred transport: " + transportType); + initHandler(); + mPreferredTransportType = transportType; + AccessNetworksManager anm = mPhone.getAccessNetworksManager(); + anm.registerForQualifiedNetworksChanged(mHandler, EVENT_QUALIFIED_NETWORKS_CHANGED); + mPhone.notifyEmergencyDomainSelected(transportType); + } +} diff --git a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..e157d24f377738683bb76945e1e51e6019bb833e --- /dev/null +++ b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; +import android.telephony.Annotation.DisconnectCauses; +import android.telephony.DomainSelectionService; +import android.telephony.DomainSelectionService.EmergencyScanType; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.ims.ImsReasonInfo; + +import com.android.internal.telephony.Phone; + +import java.util.concurrent.CompletableFuture; + +/** + * Manages the information of request and the callback binder for normal calling. + */ +public class NormalCallDomainSelectionConnection extends DomainSelectionConnection { + + private static final boolean DBG = false; + + private static final String PREFIX_WPS = "*272"; + + // WPS prefix when CLIR is being activated for the call. + private static final String PREFIX_WPS_CLIR_ACTIVATE = "*31#*272"; + + // WPS prefix when CLIR is being deactivated for the call. + private static final String PREFIX_WPS_CLIR_DEACTIVATE = "#31#*272"; + + + private @Nullable DomainSelectionConnectionCallback mCallback; + + /** + * Create an instance. + * + * @param phone For which this service is requested. + * @param controller The controller to communicate with the domain selection service. + */ + public NormalCallDomainSelectionConnection(@NonNull Phone phone, + @NonNull DomainSelectionController controller) { + super(phone, SELECTOR_TYPE_CALLING, false, controller); + mTag = "NormalCallDomainSelectionConnection"; + } + + /** {@inheritDoc} */ + @Override + public void onWlanSelected() { + CompletableFuture future = getCompletableFuture(); + future.complete(NetworkRegistrationInfo.DOMAIN_PS); + } + + /** {@inheritDoc} */ + @Override + public void onWwanSelected() { + } + + /** {@inheritDoc} */ + @Override + public void onSelectionTerminated(@DisconnectCauses int cause) { + if (mCallback != null) mCallback.onSelectionTerminated(cause); + } + + /** {@inheritDoc} */ + @Override + public void onRequestEmergencyNetworkScan(@RadioAccessNetworkType int[] preferredNetworks, + @EmergencyScanType int scanType) { + // Not expected with normal calling. + // Override to prevent abnormal behavior. + } + + /** + * Request a domain for normal call. + * + * @param attr The attributes required to determine the domain. + * @param callback A callback to receive the response. + * @return A {@link CompletableFuture} callback to receive the result. + */ + public CompletableFuture createNormalConnection( + @NonNull DomainSelectionService.SelectionAttributes attr, + @NonNull DomainSelectionConnectionCallback callback) { + mCallback = callback; + selectDomain(attr); + return getCompletableFuture(); + } + + /** + * Returns the attributes required to determine the domain for a normal call. + * + * @param slotId The slot identifier. + * @param subId The subscription identifier. + * @param callId The call identifier. + * @param number The dialed number. + * @param isVideoCall flag for video call. + * @param callFailCause The reason why the last CS attempt failed. + * @param imsReasonInfo The reason why the last PS attempt failed. + * @return The attributes required to determine the domain. + */ + public static @NonNull DomainSelectionService.SelectionAttributes getSelectionAttributes( + int slotId, int subId, @NonNull String callId, @NonNull String number, + boolean isVideoCall, int callFailCause, @Nullable ImsReasonInfo imsReasonInfo) { + + DomainSelectionService.SelectionAttributes.Builder builder = + new DomainSelectionService.SelectionAttributes.Builder( + slotId, subId, SELECTOR_TYPE_CALLING) + .setEmergency(false) + .setCallId(callId) + .setNumber(number) + .setCsDisconnectCause(callFailCause) + .setVideoCall(isVideoCall); + + if (imsReasonInfo != null) { + builder.setPsDisconnectCause(imsReasonInfo); + } + return builder.build(); + } + + /** + * Check if the call is Wireless Priority Service call + * @param dialString The number being dialed. + * @return {@code true} if dialString matches WPS pattern and {@code false} otherwise. + */ + public static boolean isWpsCall(String dialString) { + return (dialString != null) && (dialString.startsWith(PREFIX_WPS) + || dialString.startsWith(PREFIX_WPS_CLIR_ACTIVATE) + || dialString.startsWith(PREFIX_WPS_CLIR_DEACTIVATE)); + } +} diff --git a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..36a7b1738441dfea55a27240b158adb0c744fce3 --- /dev/null +++ b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_SMS; + +import android.annotation.NonNull; +import android.telephony.Annotation.DisconnectCauses; +import android.telephony.DomainSelectionService; +import android.telephony.NetworkRegistrationInfo; + +import com.android.internal.telephony.Phone; + +import java.util.concurrent.CompletableFuture; + +/** + * Manages the information of request and the callback binder for SMS. + */ +public class SmsDomainSelectionConnection extends DomainSelectionConnection { + private DomainSelectionConnectionCallback mCallback; + + public SmsDomainSelectionConnection(Phone phone, DomainSelectionController controller) { + this(phone, controller, false); + mTag = "DomainSelectionConnection-SMS"; + } + + protected SmsDomainSelectionConnection(Phone phone, DomainSelectionController controller, + boolean isEmergency) { + super(phone, SELECTOR_TYPE_SMS, isEmergency, controller); + } + + @Override + public void onWlanSelected() { + super.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS); + } + + @Override + public void onSelectionTerminated(@DisconnectCauses int cause) { + if (mCallback != null) mCallback.onSelectionTerminated(cause); + } + + @Override + public void finishSelection() { + CompletableFuture future = getCompletableFuture(); + + if (future != null && !future.isDone()) { + cancelSelection(); + } else { + super.finishSelection(); + } + } + + /** + * Requests a domain selection for SMS. + * + * @param attr The attributes required to determine the domain. + * @param callback A callback to notify an error of the domain selection. + * @return A {@link CompletableFuture} to get the selected domain + * {@link NetworkRegistrationInfo#DOMAIN_PS} or + * {@link NetworkRegistrationInfo#DOMAIN_CS}. + */ + public @NonNull CompletableFuture requestDomainSelection( + @NonNull DomainSelectionService.SelectionAttributes attr, + @NonNull DomainSelectionConnectionCallback callback) { + mCallback = callback; + selectDomain(attr); + return getCompletableFuture(); + } +} diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyConstants.java b/src/java/com/android/internal/telephony/emergency/EmergencyConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..6caf5abef7933159d192a455cdaab1dabb2f533d --- /dev/null +++ b/src/java/com/android/internal/telephony/emergency/EmergencyConstants.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.emergency; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Define the constants for emergency call domain selection. + */ +public class EmergencyConstants { + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"MODE_EMERGENCY_"}, + value = { + MODE_EMERGENCY_NONE, + MODE_EMERGENCY_WWAN, + MODE_EMERGENCY_WLAN, + MODE_EMERGENCY_CALLBACK, + }) + public @interface EmergencyMode {} + + /** + * Default value. + */ + public static final int MODE_EMERGENCY_NONE = 0; + /** + * Mode Type Emergency WWAN, indicates that the current domain selected for the Emergency call + * is cellular. + */ + public static final int MODE_EMERGENCY_WWAN = 1; + /** + * Mode Type Emergency WLAN, indicates that the current domain selected for the Emergency call + * is WLAN/WIFI. + */ + public static final int MODE_EMERGENCY_WLAN = 2; + /** + * Mode Type Emergency Callback, indicates that the current mode set request is for Emergency + * callback. + */ + public static final int MODE_EMERGENCY_CALLBACK = 3; + + /** Converts the {@link EmergencyMode} to String */ + public static String emergencyModeToString(int emcMode) { + switch (emcMode) { + case MODE_EMERGENCY_NONE: return "NONE"; + case MODE_EMERGENCY_WWAN: return "WWAN"; + case MODE_EMERGENCY_WLAN: return "WLAN"; + case MODE_EMERGENCY_CALLBACK: return "CALLBACK"; + default: return "UNKNOWN(" + emcMode + ")"; + } + } +} diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java index 8bc1dfa068be12e39348ed95cd64e04eeaffb1db..9b440017856fb093328b50b0d9df4f4ef2d4b423 100644 --- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java +++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java @@ -19,14 +19,16 @@ package com.android.internal.telephony.emergency; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; import android.os.AsyncResult; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; -import android.os.SystemProperties; import android.telephony.CarrierConfigManager; +import android.telephony.CellIdentity; import android.telephony.PhoneNumberUtils; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; @@ -35,18 +37,20 @@ import android.telephony.emergency.EmergencyNumber; import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting; import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.LocalLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; -import com.android.internal.telephony.HalVersion; import com.android.internal.telephony.LocaleTracker; 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.SubscriptionController; +import com.android.internal.telephony.metrics.EmergencyNumberStats; import com.android.internal.telephony.metrics.TelephonyMetrics; +import com.android.internal.telephony.nano.PersistAtomsProto; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.util.IndentingPrintWriter; import com.android.phone.ecc.nano.ProtobufEccData; @@ -67,6 +71,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.zip.GZIPInputStream; /** @@ -95,9 +102,18 @@ public class EmergencyNumberTracker extends Handler { private final CommandsInterface mCi; private final Phone mPhone; + private int mPhoneId; private String mCountryIso; private String mLastKnownEmergencyCountryIso = ""; private int mCurrentDatabaseVersion = INVALID_DATABASE_VERSION; + private int mCurrentOtaDatabaseVersion = INVALID_DATABASE_VERSION; + private Resources mResources = null; + /** + * Used for storing all specific mnc's along with the list of emergency numbers + * for which normal routing should be supported. + */ + private Map> mNormalRoutedNumbers = new ArrayMap<>(); + /** * Indicates if the country iso is set by another subscription. * @hide @@ -160,8 +176,10 @@ public class EmergencyNumberTracker extends Handler { public EmergencyNumberTracker(Phone phone, CommandsInterface ci) { mPhone = phone; mCi = ci; + mResources = mPhone.getContext().getResources(); if (mPhone != null) { + mPhoneId = phone.getPhoneId(); CarrierConfigManager configMgr = (CarrierConfigManager) mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); if (configMgr != null) { @@ -178,6 +196,12 @@ public class EmergencyNumberTracker extends Handler { configMgr.registerCarrierConfigChangeListener(this::post, (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigUpdated(slotIndex)); + + //register country change listener + IntentFilter filter = new IntentFilter( + TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); + mPhone.getContext().registerReceiver(mIntentReceiver, filter); + } else { loge("CarrierConfigManager is null."); } @@ -265,12 +289,7 @@ public class EmergencyNumberTracker extends Handler { @VisibleForTesting public boolean isSimAbsent() { for (Phone phone: PhoneFactory.getPhones()) { - int slotId; - if (phone.isSubscriptionManagerServiceEnabled()) { - slotId = SubscriptionManagerService.getInstance().getSlotIndex(phone.getSubId()); - } else { - slotId = SubscriptionController.getInstance().getSlotIndex(phone.getSubId()); - } + int slotId = SubscriptionManagerService.getInstance().getSlotIndex(phone.getSubId()); // If slot id is invalid, it means that there is no sim card. if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { // If there is at least one sim active, sim is not absent; it returns false @@ -285,7 +304,7 @@ public class EmergencyNumberTracker extends Handler { // If country iso has been cached when listener is set, don't need to cache the initial // country iso and initial database. if (mCountryIso == null) { - String countryForDatabaseCache = getInitialCountryIso().toLowerCase(); + String countryForDatabaseCache = getInitialCountryIso().toLowerCase(Locale.ROOT); updateEmergencyCountryIso(countryForDatabaseCache); // Use the last known country to cache the database in APM if (TextUtils.isEmpty(countryForDatabaseCache) @@ -408,7 +427,8 @@ public class EmergencyNumberTracker extends Handler { EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH, null).sendToTarget(); } - private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso) { + private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso, + int emergencyCallRouting) { String phoneNumber = eccInfo.phoneNumber.trim(); if (phoneNumber.isEmpty()) { loge("EccInfo has empty phone number."); @@ -449,13 +469,65 @@ public class EmergencyNumberTracker extends Handler { // Ignores unknown types. } } - return new EmergencyNumber(phoneNumber, countryIso, "", emergencyServiceCategoryBitmask, - new ArrayList(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, - EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + return new EmergencyNumber(phoneNumber, countryIso, "", + emergencyServiceCategoryBitmask, new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, emergencyCallRouting); + } + + /** + * Get routing type of emergency numbers from DB. Update mnc's list with numbers that are + * to supported as normal routing type in the respective mnc's. + */ + private int getRoutingInfoFromDB(EccInfo eccInfo, + Map> normalRoutedNumbers) { + int emergencyCallRouting; + switch(eccInfo.routing) + { + case EccInfo.Routing.NORMAL : + emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; + break; + case EccInfo.Routing.EMERGENCY : + emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY; + break; + default: + emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; + } + String phoneNumber = eccInfo.phoneNumber.trim(); + if (phoneNumber.isEmpty()) { + loge("EccInfo has empty phone number."); + return emergencyCallRouting; + } + + if (eccInfo.routing == EccInfo.Routing.NORMAL) { + emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; + + if (((eccInfo.normalRoutingMncs).length != 0) + && (eccInfo.normalRoutingMncs[0].length() > 0)) { + emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; + + for (String routingMnc : eccInfo.normalRoutingMncs) { + boolean mncExist = normalRoutedNumbers.containsKey(routingMnc); + Set phoneNumberList; + if (!mncExist) { + phoneNumberList = new ArraySet(); + phoneNumberList.add(phoneNumber); + normalRoutedNumbers.put(routingMnc, phoneNumberList); + } else { + phoneNumberList = normalRoutedNumbers.get(routingMnc); + if (!phoneNumberList.contains(phoneNumber)) { + phoneNumberList.add(phoneNumber); + } + } + } + logd("Normal routed mncs with phoneNumbers:" + normalRoutedNumbers); + } + } + return emergencyCallRouting; } private void cacheEmergencyDatabaseByCountry(String countryIso) { int assetsDatabaseVersion; + Map> assetNormalRoutedNumbers = new ArrayMap<>(); // Read the Asset emergency number database List updatedAssetEmergencyNumberList = new ArrayList<>(); @@ -467,12 +539,17 @@ public class EmergencyNumberTracker extends Handler { readInputStreamToByteArray(gzipInputStream)); assetsDatabaseVersion = allEccMessages.revision; logd(countryIso + " asset emergency database is loaded. Ver: " + assetsDatabaseVersion - + " Phone Id: " + mPhone.getPhoneId()); + + " Phone Id: " + mPhone.getPhoneId() + " countryIso: " + countryIso); for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) { - if (countryEccInfo.isoCode.equals(countryIso.toUpperCase())) { + if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) { for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) { + int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; + if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) { + emergencyCallRouting = getRoutingInfoFromDB(eccInfo, + assetNormalRoutedNumbers); + } updatedAssetEmergencyNumberList.add(convertEmergencyNumberFromEccInfo( - eccInfo, countryIso)); + eccInfo, countryIso, emergencyCallRouting)); } } } @@ -483,24 +560,27 @@ public class EmergencyNumberTracker extends Handler { } // Cache OTA emergency number database - int otaDatabaseVersion = cacheOtaEmergencyNumberDatabase(); + mCurrentOtaDatabaseVersion = cacheOtaEmergencyNumberDatabase(); // Use a valid database that has higher version. - if (otaDatabaseVersion == INVALID_DATABASE_VERSION + if (mCurrentOtaDatabaseVersion == INVALID_DATABASE_VERSION && assetsDatabaseVersion == INVALID_DATABASE_VERSION) { loge("No database available. Phone Id: " + mPhone.getPhoneId()); - } else if (assetsDatabaseVersion > otaDatabaseVersion) { + } else if (assetsDatabaseVersion > mCurrentOtaDatabaseVersion) { logd("Using Asset Emergency database. Version: " + assetsDatabaseVersion); mCurrentDatabaseVersion = assetsDatabaseVersion; mEmergencyNumberListFromDatabase = updatedAssetEmergencyNumberList; + mNormalRoutedNumbers.clear(); + mNormalRoutedNumbers = assetNormalRoutedNumbers; } else { - logd("Using Ota Emergency database. Version: " + otaDatabaseVersion); + logd("Using Ota Emergency database. Version: " + mCurrentOtaDatabaseVersion); } } private int cacheOtaEmergencyNumberDatabase() { ProtobufEccData.AllInfo allEccMessages = null; int otaDatabaseVersion = INVALID_DATABASE_VERSION; + Map> otaNormalRoutedNumbers = new ArrayMap<>(); // Read the OTA emergency number database List updatedOtaEmergencyNumberList = new ArrayList<>(); @@ -529,10 +609,15 @@ public class EmergencyNumberTracker extends Handler { logd(countryIso + " ota emergency database is loaded. Ver: " + otaDatabaseVersion); otaDatabaseVersion = allEccMessages.revision; for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) { - if (countryEccInfo.isoCode.equals(countryIso.toUpperCase())) { + if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) { for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) { + int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; + if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) { + emergencyCallRouting = getRoutingInfoFromDB(eccInfo, + otaNormalRoutedNumbers); + } updatedOtaEmergencyNumberList.add(convertEmergencyNumberFromEccInfo( - eccInfo, countryIso)); + eccInfo, countryIso, emergencyCallRouting)); } } } @@ -547,6 +632,8 @@ public class EmergencyNumberTracker extends Handler { && mCurrentDatabaseVersion < otaDatabaseVersion) { mCurrentDatabaseVersion = otaDatabaseVersion; mEmergencyNumberListFromDatabase = updatedOtaEmergencyNumberList; + mNormalRoutedNumbers.clear(); + mNormalRoutedNumbers = otaNormalRoutedNumbers; } return otaDatabaseVersion; } @@ -595,9 +682,9 @@ public class EmergencyNumberTracker extends Handler { private void updateEmergencyNumberListDatabaseAndNotify(String countryIso) { logd("updateEmergencyNumberListDatabaseAndNotify(): receiving countryIso: " + countryIso); - updateEmergencyCountryIso(countryIso.toLowerCase()); + updateEmergencyCountryIso(countryIso.toLowerCase(Locale.ROOT)); // Use cached country iso in APM to load emergency number database. - if (TextUtils.isEmpty(countryIso) && isAirplaneModeEnabled()) { + if (TextUtils.isEmpty(countryIso)) { countryIso = getCountryIsoForCachingDatabase(); logd("updateEmergencyNumberListDatabaseAndNotify(): using cached APM country " + countryIso); @@ -626,7 +713,8 @@ public class EmergencyNumberTracker extends Handler { private void updateOtaEmergencyNumberListDatabaseAndNotify() { logd("updateOtaEmergencyNumberListDatabaseAndNotify():" + " receiving Emegency Number database OTA update"); - if (cacheOtaEmergencyNumberDatabase() != INVALID_DATABASE_VERSION) { + mCurrentOtaDatabaseVersion = cacheOtaEmergencyNumberDatabase(); + if (mCurrentOtaDatabaseVersion != INVALID_DATABASE_VERSION) { writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase); if (!DBG) { mEmergencyNumberListDatabaseLocalLog.log( @@ -691,7 +779,11 @@ public class EmergencyNumberTracker extends Handler { } mergedEmergencyNumberList.addAll(mEmergencyNumberListWithPrefix); mergedEmergencyNumberList.addAll(mEmergencyNumberListFromTestMode); - EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); + if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) { + EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); + } else { + EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true); + } mEmergencyNumberList = mergedEmergencyNumberList; } @@ -702,11 +794,90 @@ public class EmergencyNumberTracker extends Handler { * indication not support from the HAL. */ public List getEmergencyNumberList() { + List completeEmergencyNumberList; if (!mEmergencyNumberListFromRadio.isEmpty()) { - return Collections.unmodifiableList(mEmergencyNumberList); + completeEmergencyNumberList = Collections.unmodifiableList(mEmergencyNumberList); + } else { + completeEmergencyNumberList = getEmergencyNumberListFromEccListDatabaseAndTest(); + } + if (shouldAdjustForRouting()) { + return adjustRoutingForEmergencyNumbers(completeEmergencyNumberList); + } else { + return completeEmergencyNumberList; + } + } + + /** + * Util function to check whether routing type and mnc value in emergency number needs + * to be adjusted for the current network mnc. + */ + private boolean shouldAdjustForRouting() { + if (!shouldEmergencyNumberRoutingFromDbBeIgnored() && !mNormalRoutedNumbers.isEmpty()) { + return true; + } + return false; + } + + /** + * Adjust emergency numbers with mnc and routing type based on the current network mnc. + */ + private List adjustRoutingForEmergencyNumbers( + List emergencyNumbers) { + CellIdentity cellIdentity = mPhone.getCurrentCellIdentity(); + if (cellIdentity != null) { + String networkMnc = cellIdentity.getMncString(); + Set normalRoutedPhoneNumbers = mNormalRoutedNumbers.get(networkMnc); + Set normalRoutedPhoneNumbersWithPrefix = new ArraySet(); + + if (normalRoutedPhoneNumbers != null && !normalRoutedPhoneNumbers.isEmpty()) { + for (String num : normalRoutedPhoneNumbers) { + Set phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num); + if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) { + normalRoutedPhoneNumbersWithPrefix.addAll(phoneNumbersWithPrefix); + } + } + } + List adjustedEmergencyNumberList = new ArrayList<>(); + int routing; + String mnc; + for (EmergencyNumber num : emergencyNumbers) { + routing = num.getEmergencyCallRouting(); + mnc = num.getMnc(); + if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) { + if ((normalRoutedPhoneNumbers != null + && normalRoutedPhoneNumbers.contains(num.getNumber())) + || normalRoutedPhoneNumbersWithPrefix.contains(num.getNumber())) { + routing = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; + mnc = networkMnc; + logd("adjustRoutingForEmergencyNumbers for number" + num.getNumber()); + } else if (routing == EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN) { + routing = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY; + } + } + adjustedEmergencyNumberList.add(new EmergencyNumber(num.getNumber(), + num.getCountryIso(), mnc, + num.getEmergencyServiceCategoryBitmask(), + num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), + routing)); + } + return adjustedEmergencyNumberList; } else { - return getEmergencyNumberListFromEccListDatabaseAndTest(); + return emergencyNumbers; + } + } + + + /** + * Util function to add prefix to the given emergency number. + */ + private Set addPrefixToEmergencyNumber(String number) { + Set phoneNumbersWithPrefix = new ArraySet(); + for (String prefix : mEmergencyNumberPrefix) { + if (!number.startsWith(prefix)) { + phoneNumbersWithPrefix.add(prefix + number); + } } + return phoneNumbersWithPrefix; } /** @@ -714,7 +885,7 @@ public class EmergencyNumberTracker extends Handler { * * @return {@code true} if it is; {@code false} otherwise. */ - public boolean isEmergencyNumber(String number, boolean exactMatch) { + public boolean isEmergencyNumber(String number) { if (number == null) { return false; } @@ -730,31 +901,14 @@ public class EmergencyNumberTracker extends Handler { if (!mEmergencyNumberListFromRadio.isEmpty()) { for (EmergencyNumber num : mEmergencyNumberList) { - // According to com.android.i18n.phonenumbers.ShortNumberInfo, in - // these countries, if extra digits are added to an emergency number, - // it no longer connects to the emergency service. - String countryIso = getLastKnownEmergencyCountryIso(); - if (countryIso.equals("br") || countryIso.equals("cl") - || countryIso.equals("ni")) { - exactMatch = true; - } else { - exactMatch = false || exactMatch; - } - if (exactMatch) { - if (num.getNumber().equals(number)) { - logd("Found in mEmergencyNumberList [exact match] "); - return true; - } - } else { - if (number.startsWith(num.getNumber())) { - logd("Found in mEmergencyNumberList [not exact match] "); - return true; - } + if (num.getNumber().equals(number)) { + logd("Found in mEmergencyNumberList"); + return true; } } return false; } else { - boolean inEccList = isEmergencyNumberFromEccList(number, exactMatch); + boolean inEccList = isEmergencyNumberFromEccList(number); boolean inEmergencyNumberDb = isEmergencyNumberFromDatabase(number); boolean inEmergencyNumberTestList = isEmergencyNumberForTest(number); logd("Search results - inRilEccList:" + inEccList @@ -844,6 +998,10 @@ public class EmergencyNumberTracker extends Handler { return mCurrentDatabaseVersion; } + public int getEmergencyNumberOtaDbVersion() { + return mCurrentOtaDatabaseVersion; + } + private synchronized void updateEmergencyCountryIso(String countryIso) { mCountryIso = countryIso; if (!TextUtils.isEmpty(mCountryIso)) { @@ -859,22 +1017,8 @@ public class EmergencyNumberTracker extends Handler { */ private List getEmergencyNumberListFromEccList() { List emergencyNumberList = new ArrayList<>(); - int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId()); - String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); - String emergencyNumbers = SystemProperties.get(ecclist, ""); - if (TextUtils.isEmpty(emergencyNumbers)) { - // then read-only ecclist property since old RIL only uses this - emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); - } - if (!TextUtils.isEmpty(emergencyNumbers)) { - // searches through the comma-separated list for a match, - // return true if one is found. - for (String emergencyNum : emergencyNumbers.split(",")) { - emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum)); - } - } - emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911"); + String emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911"); for (String emergencyNum : emergencyNumbers.split(",")) { emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum)); } @@ -890,15 +1034,14 @@ public class EmergencyNumberTracker extends Handler { List emergencyNumberListWithPrefix = new ArrayList<>(); if (emergencyNumberList != null) { for (EmergencyNumber num : emergencyNumberList) { - for (String prefix : mEmergencyNumberPrefix) { - // If an emergency number has started with the prefix, - // no need to apply the prefix. - if (!num.getNumber().startsWith(prefix)) { + Set phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num.getNumber()); + if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) { + for (String numberWithPrefix : phoneNumbersWithPrefix) { emergencyNumberListWithPrefix.add(new EmergencyNumber( - prefix + num.getNumber(), num.getCountryIso(), - num.getMnc(), num.getEmergencyServiceCategoryBitmask(), - num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), - num.getEmergencyCallRouting())); + numberWithPrefix, num.getCountryIso(), + num.getMnc(), num.getEmergencyServiceCategoryBitmask(), + num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), + num.getEmergencyCallRouting())); } } } @@ -917,7 +1060,7 @@ public class EmergencyNumberTracker extends Handler { } private boolean isEmergencyNumberFromDatabase(String number) { - if (!mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) { + if (mEmergencyNumberListFromDatabase.isEmpty()) { return false; } number = PhoneNumberUtils.stripSeparators(number); @@ -940,10 +1083,10 @@ public class EmergencyNumberTracker extends Handler { number = PhoneNumberUtils.stripSeparators(number); for (EmergencyNumber num : mEmergencyNumberListFromDatabase) { if (num.getNumber().equals(number)) { - return new EmergencyNumber(number, getLastKnownEmergencyCountryIso().toLowerCase(), - "", num.getEmergencyServiceCategoryBitmask(), + return new EmergencyNumber(number, getLastKnownEmergencyCountryIso() + .toLowerCase(Locale.ROOT), "", num.getEmergencyServiceCategoryBitmask(), new ArrayList(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, - EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + num.getEmergencyCallRouting()); } } return new EmergencyNumber(number, "", "", @@ -956,7 +1099,7 @@ public class EmergencyNumberTracker extends Handler { * Back-up old logics for {@link PhoneNumberUtils#isEmergencyNumberInternal} for legacy * and deprecate purpose. */ - private boolean isEmergencyNumberFromEccList(String number, boolean useExactMatch) { + private boolean isEmergencyNumberFromEccList(String number) { // If the number passed in is null, just return false: if (number == null) return false; @@ -980,59 +1123,8 @@ public class EmergencyNumberTracker extends Handler { /// @} String emergencyNumbers = ""; - int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId()); - - String ecclist = null; String countryIso = getLastKnownEmergencyCountryIso(); - - if (!mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) { - //only use ril ecc list for older devices with HAL < 1.4 - // check read-write ecclist property first - ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); - emergencyNumbers = SystemProperties.get(ecclist, ""); - - logd("slotId:" + slotId + " country:" + countryIso + " emergencyNumbers: " - + emergencyNumbers); - - if (TextUtils.isEmpty(emergencyNumbers)) { - // then read-only ecclist property since old RIL only uses this - emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); - } - - if (!TextUtils.isEmpty(emergencyNumbers)) { - // searches through the comma-separated list for a match, - // return true if one is found. - for (String emergencyNum : emergencyNumbers.split(",")) { - // According to com.android.i18n.phonenumbers.ShortNumberInfo, in - // these countries, if extra digits are added to an emergency number, - // it no longer connects to the emergency service. - if (useExactMatch || countryIso.equals("br") || countryIso.equals("cl") - || countryIso.equals("ni")) { - if (number.equals(emergencyNum)) { - return true; - } else { - for (String prefix : mEmergencyNumberPrefix) { - if (number.equals(prefix + emergencyNum)) { - return true; - } - } - } - } else { - if (number.startsWith(emergencyNum)) { - return true; - } else { - for (String prefix : mEmergencyNumberPrefix) { - if (number.startsWith(prefix + emergencyNum)) { - return true; - } - } - } - } - } - // no matches found against the list! - return false; - } - } + logd("country:" + countryIso); logd("System property doesn't provide any emergency numbers." + " Use embedded logic for determining ones."); @@ -1042,57 +1134,32 @@ public class EmergencyNumberTracker extends Handler { emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911"); for (String emergencyNum : emergencyNumbers.split(",")) { - if (useExactMatch) { - if (number.equals(emergencyNum)) { - return true; - } else { - for (String prefix : mEmergencyNumberPrefix) { - if (number.equals(prefix + emergencyNum)) { - return true; - } - } - } + if (number.equals(emergencyNum)) { + return true; } else { - if (number.startsWith(emergencyNum)) { - return true; - } else { - for (String prefix : mEmergencyNumberPrefix) { - if (number.equals(prefix + emergencyNum)) { - return true; - } + for (String prefix : mEmergencyNumberPrefix) { + if (number.equals(prefix + emergencyNum)) { + return true; } } } } - if(isSimAbsent()) { + if (isSimAbsent()) { // No ecclist system property, so use our own list. if (countryIso != null) { ShortNumberInfo info = ShortNumberInfo.getInstance(); - if (useExactMatch) { - if (info.isEmergencyNumber(number, countryIso.toUpperCase())) { - return true; - } else { - for (String prefix : mEmergencyNumberPrefix) { - if (info.isEmergencyNumber(prefix + number, countryIso.toUpperCase())) { - return true; - } - } - } - return false; + if (info.isEmergencyNumber(number, countryIso.toUpperCase(Locale.ROOT))) { + return true; } else { - if (info.connectsToEmergencyNumber(number, countryIso.toUpperCase())) { - return true; - } else { - for (String prefix : mEmergencyNumberPrefix) { - if (info.connectsToEmergencyNumber(prefix + number, - countryIso.toUpperCase())) { - return true; - } + for (String prefix : mEmergencyNumberPrefix) { + if (info.isEmergencyNumber(prefix + number, + countryIso.toUpperCase(Locale.ROOT))) { + return true; } } - return false; } + return false; } } @@ -1111,7 +1178,7 @@ public class EmergencyNumberTracker extends Handler { */ private void updateEmergencyNumberListTestModeAndNotify(int action, EmergencyNumber num) { if (action == ADD_EMERGENCY_NUMBER_TEST_MODE) { - if (!isEmergencyNumber(num.getNumber(), true)) { + if (!isEmergencyNumber(num.getNumber())) { mEmergencyNumberListFromTestMode.add(num); } } else if (action == RESET_EMERGENCY_NUMBER_TEST_MODE) { @@ -1138,7 +1205,7 @@ public class EmergencyNumberTracker extends Handler { private List getEmergencyNumberListFromEccListDatabaseAndTest() { List mergedEmergencyNumberList = getEmergencyNumberListFromEccList(); - if (mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) { + if (!mEmergencyNumberListFromDatabase.isEmpty()) { loge("getEmergencyNumberListFromEccListDatabaseAndTest: radio indication is" + " unavailable in 1.4 HAL."); mergedEmergencyNumberList.addAll(mEmergencyNumberListFromDatabase); @@ -1146,7 +1213,12 @@ public class EmergencyNumberTracker extends Handler { mEmergencyNumberListFromDatabase)); } mergedEmergencyNumberList.addAll(getEmergencyNumberListTestMode()); - EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); + + if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) { + EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); + } else { + EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true); + } return mergedEmergencyNumberList; } @@ -1162,16 +1234,16 @@ public class EmergencyNumberTracker extends Handler { return new ArrayList<>(mEmergencyNumberListFromRadio); } - private static void logd(String str) { - Rlog.d(TAG, str); + private void logd(String str) { + Rlog.d(TAG, "[" + mPhoneId + "]" + str); } - private static void logw(String str) { - Rlog.w(TAG, str); + private void logw(String str) { + Rlog.w(TAG, "[" + mPhoneId + "]" + str); } - private static void loge(String str) { - Rlog.e(TAG, str); + private void loge(String str) { + Rlog.e(TAG, "[" + mPhoneId + "]" + str); } private void writeUpdatedEmergencyNumberListMetrics( @@ -1185,6 +1257,56 @@ public class EmergencyNumberTracker extends Handler { } } + /** + * @return {@code true} if emergency numbers sourced from modem/config should be ignored. + * {@code false} if emergency numbers sourced from modem/config should not be ignored. + */ + @VisibleForTesting + public boolean shouldModemConfigEmergencyNumbersBeIgnored() { + return mResources.getBoolean(com.android.internal.R.bool + .ignore_modem_config_emergency_numbers); + } + + /** + * @return {@code true} if emergency number routing from the android emergency number + * database should be ignored. + * {@code false} if emergency number routing from the android emergency number database + * should not be ignored. + */ + @VisibleForTesting + public boolean shouldEmergencyNumberRoutingFromDbBeIgnored() { + return mResources.getBoolean(com.android.internal.R.bool + .ignore_emergency_number_routing_from_db); + } + + + /** + * @return {@code true} if determining of Urns & Service Categories while merging duplicate + * numbers should be ignored. + * {@code false} if determining of Urns & Service Categories while merging duplicate + * numbers should not be ignored. + */ + @VisibleForTesting + public boolean shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored() { + // TODO: Device config + return false; + } + + /** + * Captures the consolidated emergency numbers list and returns the array of + * {@link PersistAtomsProto.EmergencyNumber}. + */ + public PersistAtomsProto.EmergencyNumbersInfo[] getEmergencyNumbersProtoArray() { + int otaVersion = Math.max(0, getEmergencyNumberOtaDbVersion()); + int assetVersion = Math.max(0, getEmergencyNumberDbVersion()); + boolean isDbRoutingIgnored = shouldEmergencyNumberRoutingFromDbBeIgnored(); + List emergencyNumberList = getEmergencyNumberList(); + logd("log emergency number list=" + emergencyNumberList + " for otaVersion=" + otaVersion + + ", assetVersion=" + assetVersion + ", isDbRoutingIgnored=" + isDbRoutingIgnored); + return EmergencyNumberStats.getInstance().convertEmergencyNumbersListToProto( + emergencyNumberList, assetVersion, otaVersion, isDbRoutingIgnored); + } + /** * Dump Emergency Number List info in the tracking * @@ -1194,9 +1316,6 @@ public class EmergencyNumberTracker extends Handler { */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - ipw.println(" Hal Version:" + mPhone.getHalVersion()); - ipw.println(" ========================================= "); - ipw.println(" Country Iso:" + getEmergencyCountryIso()); ipw.println(" ========================================= "); @@ -1233,11 +1352,6 @@ public class EmergencyNumberTracker extends Handler { ipw.decreaseIndent(); ipw.println(" ========================================= "); - int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId()); - String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); - ipw.println(" ril.ecclist: " + SystemProperties.get(ecclist, "")); - ipw.println(" ========================================= "); - ipw.println("Emergency Number List for Phone" + "(" + mPhone.getPhoneId() + ")"); ipw.increaseIndent(); ipw.println(getEmergencyNumberList()); diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..96cd8808826bd87164df88c3716faa78f4f4b789 --- /dev/null +++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java @@ -0,0 +1,1208 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.emergency; + +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_NONE; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PersistableBundle; +import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.sysprop.TelephonyProperties; +import android.telephony.Annotation.DisconnectCauses; +import android.telephony.CarrierConfigManager; +import android.telephony.DisconnectCause; +import android.telephony.EmergencyRegResult; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.telephony.emergency.EmergencyNumber; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Call; +import com.android.internal.telephony.GsmCdmaPhone; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.data.PhoneSwitcher; +import com.android.telephony.Rlog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +/** + * Tracks the emergency call state and notifies listeners of changes to the emergency mode. + */ +public class EmergencyStateTracker { + + private static final String TAG = "EmergencyStateTracker"; + + /** + * Timeout before we continue with the emergency call without waiting for DDS switch response + * from the modem. + */ + private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000; + /** Default value for if Emergency Callback Mode is supported. */ + private static final boolean DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED = true; + /** Default Emergency Callback Mode exit timeout value. */ + private static final long DEFAULT_ECM_EXIT_TIMEOUT_MS = 300000; + + /** The emergency types used when setting the emergency mode on modem. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "EMERGENCY_TYPE_", + value = { + EMERGENCY_TYPE_CALL, + EMERGENCY_TYPE_SMS}) + public @interface EmergencyType {} + + /** Indicates the emergency type is call. */ + public static final int EMERGENCY_TYPE_CALL = 1; + /** Indicates the emergency type is SMS. */ + public static final int EMERGENCY_TYPE_SMS = 2; + + private static EmergencyStateTracker INSTANCE = null; + + private final Context mContext; + private final CarrierConfigManager mConfigManager; + private final Handler mHandler; + private final boolean mIsSuplDdsSwitchRequiredForEmergencyCall; + private final PowerManager.WakeLock mWakeLock; + private RadioOnHelper mRadioOnHelper; + @EmergencyConstants.EmergencyMode + private int mEmergencyMode = MODE_EMERGENCY_NONE; + private boolean mWasEmergencyModeSetOnModem; + private EmergencyRegResult mLastEmergencyRegResult; + private boolean mIsEmergencyModeInProgress; + private boolean mIsEmergencyCallStartedDuringEmergencySms; + + /** For emergency calls */ + private final long mEcmExitTimeoutMs; + // A runnable which is used to automatically exit from Ecm after a period of time. + private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode; + // Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}. + private final Set mActiveEmergencyCalls = new ArraySet<>(); + private Phone mPhone; + // Tracks ongoing emergency callId to handle a second emergency call + private String mOngoingCallId; + // Domain of the active emergency call. Assuming here that there will only be one domain active. + private int mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN; + private CompletableFuture mCallEmergencyModeFuture; + private boolean mIsInEmergencyCall; + private boolean mIsInEcm; + private boolean mIsTestEmergencyNumber; + private Runnable mOnEcmExitCompleteRunnable; + + /** For emergency SMS */ + private final Set mOngoingEmergencySmsIds = new ArraySet<>(); + private Phone mSmsPhone; + private CompletableFuture mSmsEmergencyModeFuture; + private boolean mIsTestEmergencyNumberForSms; + + /** + * Listens for Emergency Callback Mode state change intents + */ + private final BroadcastReceiver mEcmExitReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals( + TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) { + + boolean isInEcm = intent.getBooleanExtra( + TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false); + Rlog.d(TAG, "Received ACTION_EMERGENCY_CALLBACK_MODE_CHANGED isInEcm = " + isInEcm); + + // If we exit ECM mode, notify all connections. + if (!isInEcm) { + exitEmergencyCallbackMode(); + } + } + } + }; + + /** PhoneFactory Dependencies for testing. */ + @VisibleForTesting + public interface PhoneFactoryProxy { + Phone[] getPhones(); + } + + private PhoneFactoryProxy mPhoneFactoryProxy = PhoneFactory::getPhones; + + /** PhoneSwitcher dependencies for testing. */ + @VisibleForTesting + public interface PhoneSwitcherProxy { + + PhoneSwitcher getPhoneSwitcher(); + } + + private PhoneSwitcherProxy mPhoneSwitcherProxy = PhoneSwitcher::getInstance; + + /** + * TelephonyManager dependencies for testing. + */ + @VisibleForTesting + public interface TelephonyManagerProxy { + int getPhoneCount(); + } + + private final TelephonyManagerProxy mTelephonyManagerProxy; + + private static class TelephonyManagerProxyImpl implements TelephonyManagerProxy { + private final TelephonyManager mTelephonyManager; + + + TelephonyManagerProxyImpl(Context context) { + mTelephonyManager = new TelephonyManager(context); + } + + @Override + public int getPhoneCount() { + return mTelephonyManager.getActiveModemCount(); + } + } + + /** + * Return the handler for testing. + */ + @VisibleForTesting + public Handler getHandler() { + return mHandler; + } + + @VisibleForTesting + public static final int MSG_SET_EMERGENCY_MODE_DONE = 1; + @VisibleForTesting + public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 2; + @VisibleForTesting + public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3; + + private class MyHandler extends Handler { + + MyHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_EMERGENCY_MODE_DONE: { + AsyncResult ar = (AsyncResult) msg.obj; + Integer emergencyType = (Integer) ar.userObj; + Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE_DONE for " + + emergencyTypeToString(emergencyType)); + if (ar.exception == null) { + mLastEmergencyRegResult = (EmergencyRegResult) ar.result; + } else { + mLastEmergencyRegResult = null; + Rlog.w(TAG, "LastEmergencyRegResult not set. AsyncResult.exception: " + + ar.exception); + } + setEmergencyModeInProgress(false); + + if (emergencyType == EMERGENCY_TYPE_CALL) { + setIsInEmergencyCall(true); + completeEmergencyMode(emergencyType); + + // Case 1) When the emergency call is setting the emergency mode and + // the emergency SMS is being sent, completes the SMS future also. + // Case 2) When the emergency SMS is setting the emergency mode and + // the emergency call is beint started, the SMS request is cancelled and + // the call request will be handled. + if (mSmsPhone != null) { + completeEmergencyMode(EMERGENCY_TYPE_SMS); + } + } else if (emergencyType == EMERGENCY_TYPE_SMS) { + if (mPhone != null && mSmsPhone != null) { + // Clear call phone temporarily to exit the emergency mode + // if the emergency call is started. + if (mIsEmergencyCallStartedDuringEmergencySms) { + Phone phone = mPhone; + mPhone = null; + exitEmergencyMode(mSmsPhone, emergencyType); + // Restore call phone for further use. + mPhone = phone; + + if (!isSamePhone(mPhone, mSmsPhone)) { + completeEmergencyMode(emergencyType, + DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED); + } + } else { + completeEmergencyMode(emergencyType); + } + break; + } else { + completeEmergencyMode(emergencyType); + } + + if (mIsEmergencyCallStartedDuringEmergencySms) { + mIsEmergencyCallStartedDuringEmergencySms = false; + turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, + mIsTestEmergencyNumber); + } + } + break; + } + case MSG_EXIT_EMERGENCY_MODE_DONE: { + AsyncResult ar = (AsyncResult) msg.obj; + Integer emergencyType = (Integer) ar.userObj; + Rlog.v(TAG, "MSG_EXIT_EMERGENCY_MODE_DONE for " + + emergencyTypeToString(emergencyType)); + setEmergencyModeInProgress(false); + + if (emergencyType == EMERGENCY_TYPE_CALL) { + setIsInEmergencyCall(false); + if (mOnEcmExitCompleteRunnable != null) { + mOnEcmExitCompleteRunnable.run(); + mOnEcmExitCompleteRunnable = null; + } + } else if (emergencyType == EMERGENCY_TYPE_SMS) { + if (mIsEmergencyCallStartedDuringEmergencySms) { + mIsEmergencyCallStartedDuringEmergencySms = false; + turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, + mIsTestEmergencyNumber); + } + } + break; + } + case MSG_SET_EMERGENCY_CALLBACK_MODE_DONE: { + AsyncResult ar = (AsyncResult) msg.obj; + Integer emergencyType = (Integer) ar.userObj; + Rlog.v(TAG, "MSG_SET_EMERGENCY_CALLBACK_MODE_DONE for " + + emergencyTypeToString(emergencyType)); + setEmergencyModeInProgress(false); + // When the emergency callback mode is in progress and the emergency SMS is + // started, it needs to be completed here for the emergency SMS. + if (mSmsPhone != null) { + completeEmergencyMode(EMERGENCY_TYPE_SMS); + } + break; + } + default: + break; + } + } + } + + /** + * Creates the EmergencyStateTracker singleton instance. + * + * @param context The context of the application. + * @param isSuplDdsSwitchRequiredForEmergencyCall Whether gnss supl requires default data for + * emergency call. + */ + public static void make(Context context, boolean isSuplDdsSwitchRequiredForEmergencyCall) { + if (INSTANCE == null) { + INSTANCE = new EmergencyStateTracker(context, Looper.myLooper(), + isSuplDdsSwitchRequiredForEmergencyCall); + } + } + + /** + * Returns the singleton instance of EmergencyStateTracker. + * + * @return {@link EmergencyStateTracker} instance. + */ + public static EmergencyStateTracker getInstance() { + if (INSTANCE == null) { + throw new IllegalStateException("EmergencyStateTracker is not ready!"); + } + return INSTANCE; + } + + /** + * Initializes EmergencyStateTracker. + */ + private EmergencyStateTracker(Context context, Looper looper, + boolean isSuplDdsSwitchRequiredForEmergencyCall) { + mEcmExitTimeoutMs = DEFAULT_ECM_EXIT_TIMEOUT_MS; + mContext = context; + mHandler = new MyHandler(looper); + mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall; + + PowerManager pm = context.getSystemService(PowerManager.class); + mWakeLock = (pm != null) ? pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "telephony:" + TAG) : null; + mConfigManager = context.getSystemService(CarrierConfigManager.class); + + // Register receiver for ECM exit. + IntentFilter filter = new IntentFilter(); + filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + context.registerReceiver(mEcmExitReceiver, filter, null, mHandler); + mTelephonyManagerProxy = new TelephonyManagerProxyImpl(context); + } + + /** + * Initializes EmergencyStateTracker with injections for testing. + * + * @param context The context of the application. + * @param looper The {@link Looper} of the application. + * @param isSuplDdsSwitchRequiredForEmergencyCall Whether gnss supl requires default data for + * emergency call. + * @param phoneFactoryProxy The {@link PhoneFactoryProxy} to be injected. + * @param phoneSwitcherProxy The {@link PhoneSwitcherProxy} to be injected. + * @param telephonyManagerProxy The {@link TelephonyManagerProxy} to be + * injected. + * @param radioOnHelper The {@link RadioOnHelper} to be injected. + */ + @VisibleForTesting + public EmergencyStateTracker(Context context, Looper looper, + boolean isSuplDdsSwitchRequiredForEmergencyCall, PhoneFactoryProxy phoneFactoryProxy, + PhoneSwitcherProxy phoneSwitcherProxy, TelephonyManagerProxy telephonyManagerProxy, + RadioOnHelper radioOnHelper, long ecmExitTimeoutMs) { + mContext = context; + mHandler = new MyHandler(looper); + mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall; + mPhoneFactoryProxy = phoneFactoryProxy; + mPhoneSwitcherProxy = phoneSwitcherProxy; + mTelephonyManagerProxy = telephonyManagerProxy; + mRadioOnHelper = radioOnHelper; + mEcmExitTimeoutMs = ecmExitTimeoutMs; + mWakeLock = null; // Don't declare a wakelock in tests + mConfigManager = context.getSystemService(CarrierConfigManager.class); + IntentFilter filter = new IntentFilter(); + filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + context.registerReceiver(mEcmExitReceiver, filter, null, mHandler); + } + + /** + * Starts the process of an emergency call. + * + *

+ * Handles turning on radio and switching DDS. + * + * @param phone the {@code Phone} on which to process the emergency call. + * @param callId the call id on which to process the emergency call. + * @param isTestEmergencyNumber whether this is a test emergency number. + * @return a {@code CompletableFuture} that results in {@code DisconnectCause.NOT_DISCONNECTED} + * if emergency call successfully started. + */ + public CompletableFuture startEmergencyCall(@NonNull Phone phone, + @NonNull String callId, boolean isTestEmergencyNumber) { + Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId() + ", callId=" + callId); + + if (mPhone != null) { + // Create new future to return as to not interfere with any uncompleted futures. + // Case1) When 2nd emergency call is initiated during an active call on the same phone. + // Case2) While the device is in ECBM, an emergency call is initiated on the same phone. + if (isSamePhone(mPhone, phone) && (!mActiveEmergencyCalls.isEmpty() || isInEcm())) { + mOngoingCallId = callId; + mIsTestEmergencyNumber = isTestEmergencyNumber; + return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED); + } + + Rlog.e(TAG, "startEmergencyCall failed. Existing emergency call in progress."); + return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); + } + + mCallEmergencyModeFuture = new CompletableFuture<>(); + + if (mSmsPhone != null) { + mIsEmergencyCallStartedDuringEmergencySms = true; + // Case1) While exiting the emergency mode on the other phone, + // the emergency mode for this call will be restarted after the exit complete. + // Case2) While entering the emergency mode on the other phone, + // exit the emergency mode when receiving the result of setting the emergency mode and + // the emergency mode for this call will be restarted after the exit complete. + if (isInEmergencyMode() && !isEmergencyModeInProgress()) { + exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS); + } + + mPhone = phone; + mOngoingCallId = callId; + mIsTestEmergencyNumber = isTestEmergencyNumber; + return mCallEmergencyModeFuture; + } + + mPhone = phone; + mOngoingCallId = callId; + mIsTestEmergencyNumber = isTestEmergencyNumber; + turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber); + return mCallEmergencyModeFuture; + } + + /** + * Ends emergency call. + * + *

+ * Enter ECM only once all active emergency calls have ended. If a call never reached + * {@link Call.State#ACTIVE}, then no need to enter ECM. + * + * @param callId the call id on which to end the emergency call. + */ + public void endCall(@NonNull String callId) { + boolean wasActive = mActiveEmergencyCalls.remove(callId); + + if (Objects.equals(mOngoingCallId, callId)) { + mOngoingCallId = null; + } + + if (wasActive && mActiveEmergencyCalls.isEmpty() + && isEmergencyCallbackModeSupported()) { + enterEmergencyCallbackMode(); + + if (mOngoingCallId == null) { + mIsEmergencyCallStartedDuringEmergencySms = false; + mCallEmergencyModeFuture = null; + } + } else if (mOngoingCallId == null) { + if (isInEcm()) { + mIsEmergencyCallStartedDuringEmergencySms = false; + mCallEmergencyModeFuture = null; + // If the emergency call was initiated during the emergency callback mode, + // the emergency callback mode should be restored when the emergency call is ended. + if (mActiveEmergencyCalls.isEmpty()) { + setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK, + MSG_SET_EMERGENCY_CALLBACK_MODE_DONE); + } + } else { + exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL); + clearEmergencyCallInfo(); + } + } + } + + private void clearEmergencyCallInfo() { + mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN; + mIsTestEmergencyNumber = false; + mIsEmergencyCallStartedDuringEmergencySms = false; + mCallEmergencyModeFuture = null; + mOngoingCallId = null; + mPhone = null; + } + + private void switchDdsAndSetEmergencyMode(Phone phone, @EmergencyType int emergencyType) { + switchDdsDelayed(phone, result -> { + Rlog.i(TAG, "switchDdsDelayed: result = " + result); + if (!result) { + // DDS Switch timed out/failed, but continue with call as it may still succeed. + Rlog.e(TAG, "DDS Switch failed."); + } + // Once radio is on and DDS switched, must call setEmergencyMode() before selecting + // emergency domain. EmergencyRegResult is required to determine domain and this is the + // only API that can receive it before starting domain selection. Once domain selection + // is finished, the actual emergency mode will be set when onEmergencyTransportChanged() + // is called. + setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN, + MSG_SET_EMERGENCY_MODE_DONE); + }); + } + + /** + * Triggers modem to set new emergency mode. + * + * @param phone the {@code Phone} to set the emergency mode on modem. + * @param emergencyType the emergency type to identify an emergency call or SMS. + * @param mode the new emergency mode. + * @param msg the message to be sent once mode has been set. + */ + private void setEmergencyMode(Phone phone, @EmergencyType int emergencyType, + @EmergencyConstants.EmergencyMode int mode, int msg) { + Rlog.i(TAG, "setEmergencyMode from " + mEmergencyMode + " to " + mode + " for " + + emergencyTypeToString(emergencyType)); + + if (mEmergencyMode == mode) { + return; + } + mEmergencyMode = mode; + setEmergencyModeInProgress(true); + + Message m = mHandler.obtainMessage(msg, Integer.valueOf(emergencyType)); + if ((mIsTestEmergencyNumber && emergencyType == EMERGENCY_TYPE_CALL) + || (mIsTestEmergencyNumberForSms && emergencyType == EMERGENCY_TYPE_SMS)) { + Rlog.d(TAG, "TestEmergencyNumber for " + emergencyTypeToString(emergencyType) + + ": Skipping setting emergency mode on modem."); + // Send back a response for the command, but with null information + AsyncResult.forMessage(m, null, null); + // Ensure that we do not accidentally block indefinitely when trying to validate test + // emergency numbers + m.sendToTarget(); + return; + } + + mWasEmergencyModeSetOnModem = true; + phone.setEmergencyMode(mode, m); + } + + private void completeEmergencyMode(@EmergencyType int emergencyType) { + completeEmergencyMode(emergencyType, DisconnectCause.NOT_DISCONNECTED); + } + + private void completeEmergencyMode(@EmergencyType int emergencyType, + @DisconnectCauses int result) { + if (emergencyType == EMERGENCY_TYPE_CALL) { + if (mCallEmergencyModeFuture != null && !mCallEmergencyModeFuture.isDone()) { + mCallEmergencyModeFuture.complete(result); + } + + if (result != DisconnectCause.NOT_DISCONNECTED) { + clearEmergencyCallInfo(); + } + } else if (emergencyType == EMERGENCY_TYPE_SMS) { + if (mSmsEmergencyModeFuture != null && !mSmsEmergencyModeFuture.isDone()) { + mSmsEmergencyModeFuture.complete(result); + } + + if (result != DisconnectCause.NOT_DISCONNECTED) { + clearEmergencySmsInfo(); + } + } + } + + /** + * Checks if the device is currently in the emergency mode or not. + */ + @VisibleForTesting + public boolean isInEmergencyMode() { + return mEmergencyMode != MODE_EMERGENCY_NONE; + } + + /** + * Sets the flag to inidicate whether setting the emergency mode on modem is in progress or not. + */ + private void setEmergencyModeInProgress(boolean isEmergencyModeInProgress) { + mIsEmergencyModeInProgress = isEmergencyModeInProgress; + } + + /** + * Checks whether setting the emergency mode on modem is in progress or not. + */ + private boolean isEmergencyModeInProgress() { + return mIsEmergencyModeInProgress; + } + + /** + * Notifies external app listeners of emergency mode changes. + * + * @param isInEmergencyCall a flag to indicate whether there is an active emergency call. + */ + private void setIsInEmergencyCall(boolean isInEmergencyCall) { + mIsInEmergencyCall = isInEmergencyCall; + } + + /** + * Checks if there is an ongoing emergency call. + * + * @return true if in emergency call + */ + public boolean isInEmergencyCall() { + return mIsInEmergencyCall; + } + + /** + * Triggers modem to exit emergency mode. + * + * @param phone the {@code Phone} to exit the emergency mode. + * @param emergencyType the emergency type to identify an emergency call or SMS. + */ + private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType) { + Rlog.i(TAG, "exitEmergencyMode for " + emergencyTypeToString(emergencyType)); + + if (emergencyType == EMERGENCY_TYPE_CALL) { + if (mSmsPhone != null && isSamePhone(phone, mSmsPhone)) { + // Waits for exiting the emergency mode until the emergency SMS is ended. + Rlog.i(TAG, "exitEmergencyMode: waits for emergency SMS end."); + setIsInEmergencyCall(false); + return; + } + } else if (emergencyType == EMERGENCY_TYPE_SMS) { + if (mPhone != null && isSamePhone(phone, mPhone)) { + // Waits for exiting the emergency mode until the emergency call is ended. + Rlog.i(TAG, "exitEmergencyMode: waits for emergency call end."); + return; + } + } + + if (mEmergencyMode == MODE_EMERGENCY_NONE) { + return; + } + mEmergencyMode = MODE_EMERGENCY_NONE; + setEmergencyModeInProgress(true); + + Message m = mHandler.obtainMessage( + MSG_EXIT_EMERGENCY_MODE_DONE, Integer.valueOf(emergencyType)); + if (!mWasEmergencyModeSetOnModem) { + Rlog.d(TAG, "Emergency mode was not set on modem: Skipping exiting emergency mode."); + // Send back a response for the command, but with null information + AsyncResult.forMessage(m, null, null); + // Ensure that we do not accidentally block indefinitely when trying to validate + // the exit condition. + m.sendToTarget(); + return; + } + + mWasEmergencyModeSetOnModem = false; + phone.exitEmergencyMode(m); + } + + /** Returns last {@link EmergencyRegResult} as set by {@code setEmergencyMode()}. */ + public EmergencyRegResult getEmergencyRegResult() { + return mLastEmergencyRegResult; + } + + /** + * Handles emergency transport change by setting new emergency mode. + * + * @param emergencyType the emergency type to identify an emergency call or SMS + * @param mode the new emergency mode + */ + public void onEmergencyTransportChanged(@EmergencyType int emergencyType, + @EmergencyConstants.EmergencyMode int mode) { + if (mHandler.getLooper().isCurrentThread()) { + Phone phone = null; + if (emergencyType == EMERGENCY_TYPE_CALL) { + phone = mPhone; + } else if (emergencyType == EMERGENCY_TYPE_SMS) { + phone = mSmsPhone; + } + + if (phone != null) { + setEmergencyMode(phone, emergencyType, mode, MSG_SET_EMERGENCY_MODE_DONE); + } + } else { + mHandler.post(() -> { + onEmergencyTransportChanged(emergencyType, mode); + }); + } + } + + /** + * Notify the tracker that the emergency call domain has been updated. + * @param phoneType The new PHONE_TYPE_* of the call. + * @param callId The ID of the call + */ + public void onEmergencyCallDomainUpdated(int phoneType, String callId) { + Rlog.d(TAG, "domain update for callId: " + callId); + int domain = -1; + switch(phoneType) { + case (PhoneConstants.PHONE_TYPE_CDMA_LTE): + //fallthrough + case (PhoneConstants.PHONE_TYPE_GSM): + //fallthrough + case (PhoneConstants.PHONE_TYPE_CDMA): { + domain = NetworkRegistrationInfo.DOMAIN_CS; + break; + } + case (PhoneConstants.PHONE_TYPE_IMS): { + domain = NetworkRegistrationInfo.DOMAIN_PS; + break; + } + default: { + Rlog.w(TAG, "domain updated: Unexpected phoneType:" + phoneType); + } + } + if (mEmergencyCallDomain == domain) return; + Rlog.i(TAG, "domain updated: from " + mEmergencyCallDomain + " to " + domain); + mEmergencyCallDomain = domain; + } + + /** + * Handles emergency call state change. + * + * @param state the new call state + * @param callId the callId whose state has changed + */ + public void onEmergencyCallStateChanged(Call.State state, String callId) { + if (state == Call.State.ACTIVE) { + mActiveEmergencyCalls.add(callId); + } + } + + /** + * Returns {@code true} if device and carrier support emergency callback mode. + */ + private boolean isEmergencyCallbackModeSupported() { + return getConfig(mPhone.getSubId(), + CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL, + DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED); + } + + /** + * Trigger entry into emergency callback mode. + */ + private void enterEmergencyCallbackMode() { + Rlog.d(TAG, "enter ECBM"); + setIsInEmergencyCall(false); + // Check if not in ECM already. + if (!isInEcm()) { + setIsInEcm(true); + if (!mPhone.getUnitTestMode()) { + TelephonyProperties.in_ecm_mode(true); + } + + // Notify listeners of the entrance to ECM. + sendEmergencyCallbackModeChange(); + if (isInImsEcm()) { + // emergency call registrants are not notified of new emergency call until entering + // ECBM (see ImsPhone#handleEnterEmergencyCallbackMode) + ((GsmCdmaPhone) mPhone).notifyEmergencyCallRegistrants(true); + } + + // Set emergency mode on modem. + setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK, + MSG_SET_EMERGENCY_CALLBACK_MODE_DONE); + + // Post this runnable so we will automatically exit if no one invokes + // exitEmergencyCallbackMode() directly. + long delayInMillis = TelephonyProperties.ecm_exit_timer() + .orElse(mEcmExitTimeoutMs); + mHandler.postDelayed(mExitEcmRunnable, delayInMillis); + + // We don't want to go to sleep while in ECM. + if (mWakeLock != null) mWakeLock.acquire(delayInMillis); + } + } + + /** + * Exits emergency callback mode and notifies relevant listeners. + */ + public void exitEmergencyCallbackMode() { + Rlog.d(TAG, "exit ECBM"); + // Remove pending exit ECM runnable, if any. + mHandler.removeCallbacks(mExitEcmRunnable); + + if (isInEcm()) { + setIsInEcm(false); + if (!mPhone.getUnitTestMode()) { + TelephonyProperties.in_ecm_mode(false); + } + + // Release wakeLock. + if (mWakeLock != null && mWakeLock.isHeld()) { + try { + mWakeLock.release(); + } catch (Exception e) { + // Ignore the exception if the system has already released this WakeLock. + Rlog.d(TAG, "WakeLock already released: " + e.toString()); + } + } + + GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) mPhone; + // Send intents that ECM has changed. + sendEmergencyCallbackModeChange(); + gsmCdmaPhone.notifyEmergencyCallRegistrants(false); + + // Exit emergency mode on modem. + exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL); + } + + mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN; + mIsTestEmergencyNumber = false; + mPhone = null; + } + + /** + * Exits emergency callback mode and triggers runnable after exit response is received. + */ + public void exitEmergencyCallbackMode(Runnable onComplete) { + mOnEcmExitCompleteRunnable = onComplete; + exitEmergencyCallbackMode(); + } + + /** + * Sends intents that emergency callback mode changed. + */ + private void sendEmergencyCallbackModeChange() { + Rlog.d(TAG, "sendEmergencyCallbackModeChange: isInEcm=" + isInEcm()); + + Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, isInEcm()); + SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + + /** + * Returns {@code true} if currently in emergency callback mode. + * + *

+ * This is a period where the phone should be using as little power as possible and be ready to + * receive an incoming call from the emergency operator. + */ + public boolean isInEcm() { + return mIsInEcm; + } + + /** + * Sets the emergency callback mode state. + * + * @param isInEcm {@code true} if currently in emergency callback mode, {@code false} otherwise. + */ + private void setIsInEcm(boolean isInEcm) { + mIsInEcm = isInEcm; + } + + /** + * Returns {@code true} if currently in emergency callback mode over PS + */ + public boolean isInImsEcm() { + return mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_PS && isInEcm(); + } + + /** + * Returns {@code true} if currently in emergency callback mode over CS + */ + public boolean isInCdmaEcm() { + // Phone can be null in the case where we are not actively tracking an emergency call. + if (mPhone == null) return false; + // Ensure that this method doesn't return true when we are attached to GSM. + return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA + && mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_CS && isInEcm(); + } + + /** + * Starts the process of an emergency SMS. + * + * @param phone the {@code Phone} on which to process the emergency SMS. + * @param smsId the SMS id on which to process the emergency SMS. + * @param isTestEmergencyNumber whether this is a test emergency number. + * @return A {@code CompletableFuture} that results in {@code DisconnectCause.NOT_DISCONNECTED} + * if the emergency SMS is successfully started. + */ + public CompletableFuture startEmergencySms(@NonNull Phone phone, @NonNull String smsId, + boolean isTestEmergencyNumber) { + Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId); + + // When an emergency call is in progress, it checks whether an emergency call is already in + // progress on the different phone. + if (mPhone != null && !isSamePhone(mPhone, phone)) { + Rlog.e(TAG, "Emergency call is in progress on the other slot."); + return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); + } + + // When an emergency SMS is in progress, it checks whether an emergency SMS is already in + // progress on the different phone. + if (mSmsPhone != null && !isSamePhone(mSmsPhone, phone)) { + Rlog.e(TAG, "Emergency SMS is in progress on the other slot."); + return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); + } + + // When the previous emergency SMS is not completed yet, + // this new request will not be allowed. + if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress()) { + Rlog.e(TAG, "Existing emergency SMS is in progress."); + return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); + } + + mSmsPhone = phone; + mIsTestEmergencyNumberForSms = isTestEmergencyNumber; + mOngoingEmergencySmsIds.add(smsId); + + // When the emergency mode is already set by the previous emergency call or SMS, + // completes the future immediately. + if (isInEmergencyMode() && !isEmergencyModeInProgress()) { + return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED); + } + + mSmsEmergencyModeFuture = new CompletableFuture<>(); + if (!isInEmergencyMode()) { + setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN, + MSG_SET_EMERGENCY_MODE_DONE); + } + return mSmsEmergencyModeFuture; + } + + /** + * Ends an emergency SMS. + * This should be called once an emergency SMS is sent. + * + * @param smsId the SMS id on which to end the emergency SMS. + * @param emergencyNumber the emergency number which was used for the emergency SMS. + */ + public void endSms(@NonNull String smsId, EmergencyNumber emergencyNumber) { + mOngoingEmergencySmsIds.remove(smsId); + + // If the outgoing emergency SMSs are empty, we can try to exit the emergency mode. + if (mOngoingEmergencySmsIds.isEmpty()) { + if (isInEcm()) { + // When the emergency mode is not in MODE_EMERGENCY_CALLBACK, + // it needs to notify the emergency callback mode to modem. + if (mActiveEmergencyCalls.isEmpty() && mOngoingCallId == null) { + setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK, + MSG_SET_EMERGENCY_CALLBACK_MODE_DONE); + } + } else { + exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS); + } + + clearEmergencySmsInfo(); + } + } + + private void clearEmergencySmsInfo() { + mOngoingEmergencySmsIds.clear(); + mIsTestEmergencyNumberForSms = false; + mSmsEmergencyModeFuture = null; + mSmsPhone = null; + } + + /** + * Returns {@code true} if any phones from PhoneFactory have radio on. + */ + private boolean isRadioOn() { + boolean result = false; + for (Phone phone : mPhoneFactoryProxy.getPhones()) { + result |= phone.isRadioOn(); + } + return result; + } + + /** + * Returns {@code true} if airplane mode is on. + */ + private boolean isAirplaneModeOn(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) > 0; + } + + /** + * Ensures that the radio is switched on and that DDS is switched for emergency call/SMS. + * + *

+ * Once radio is on and DDS switched, must call setEmergencyMode() before completing the future + * and selecting emergency domain. EmergencyRegResult is required to determine domain and + * setEmergencyMode() is the only API that can receive it before starting domain selection. + * Once domain selection is finished, the actual emergency mode will be set when + * onEmergencyTransportChanged() is called. + * + * @param phone the {@code Phone} for the emergency call/SMS. + * @param emergencyType the emergency type to identify an emergency call or SMS. + * @param isTestEmergencyNumber a flag to inidicate whether the emergency call/SMS uses the test + * emergency number. + */ + private void turnOnRadioAndSwitchDds(Phone phone, @EmergencyType int emergencyType, + boolean isTestEmergencyNumber) { + final boolean isAirplaneModeOn = isAirplaneModeOn(mContext); + boolean needToTurnOnRadio = !isRadioOn() || isAirplaneModeOn; + + if (needToTurnOnRadio) { + Rlog.i(TAG, "turnOnRadioAndSwitchDds: phoneId=" + phone.getPhoneId() + " for " + + emergencyTypeToString(emergencyType)); + if (mRadioOnHelper == null) { + mRadioOnHelper = new RadioOnHelper(mContext); + } + + mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() { + @Override + public void onComplete(RadioOnStateListener listener, boolean isRadioReady) { + if (!isRadioReady) { + // Could not turn radio on + Rlog.e(TAG, "Failed to turn on radio."); + completeEmergencyMode(emergencyType, DisconnectCause.POWER_OFF); + } else { + switchDdsAndSetEmergencyMode(phone, emergencyType); + } + } + + @Override + public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) { + // We currently only look to make sure that the radio is on before dialing. We + // should be able to make emergency calls at any time after the radio has been + // powered on and isn't in the UNAVAILABLE state, even if it is reporting the + // OUT_OF_SERVICE state. + return phone.getServiceStateTracker().isRadioOn(); + } + + @Override + public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) { + return true; + } + }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, 0); + } else { + switchDdsAndSetEmergencyMode(phone, emergencyType); + } + } + + /** + * If needed, block until the default data is switched for outgoing emergency call, or + * timeout expires. + * + * @param phone The Phone to switch the DDS on. + * @param completeConsumer The consumer to call once the default data subscription has been + * switched, provides {@code true} result if the switch happened + * successfully or {@code false} if the operation timed out/failed. + */ + @VisibleForTesting + public void switchDdsDelayed(Phone phone, Consumer completeConsumer) { + if (phone == null) { + // Do not block indefinitely. + completeConsumer.accept(false); + } + try { + // Waiting for PhoneSwitcher to complete the operation. + CompletableFuture future = possiblyOverrideDefaultDataForEmergencyCall(phone); + // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait + // indefinitely for the future to complete. Instead, set a timeout that will complete + // the future as to not block the outgoing call indefinitely. + CompletableFuture timeout = new CompletableFuture<>(); + mHandler.postDelayed(() -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS); + // Also ensure that the Consumer is completed on the main thread. + CompletableFuture unused = future.acceptEitherAsync(timeout, completeConsumer, + mHandler::post); + } catch (Exception e) { + Rlog.w(TAG, "switchDdsDelayed - exception= " + e.getMessage()); + } + } + + /** + * If needed, block until Default Data subscription is switched for outgoing emergency call. + * + *

+ * In some cases, we need to try to switch the Default Data subscription before placing the + * emergency call on DSDS devices. This includes the following situation: - The modem does not + * support processing GNSS SUPL requests on the non-default data subscription. For some carriers + * that do not provide a control plane fallback mechanism, the SUPL request will be dropped and + * we will not be able to get the user's location for the emergency call. In this case, we need + * to swap default data temporarily. + * + * @param phone Evaluates whether or not the default data should be moved to the phone + * specified. Should not be null. + */ + private CompletableFuture possiblyOverrideDefaultDataForEmergencyCall( + @NonNull Phone phone) { + int phoneCount = mTelephonyManagerProxy.getPhoneCount(); + // Do not override DDS if this is a single SIM device. + if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) { + return CompletableFuture.completedFuture(Boolean.TRUE); + } + + // Do not switch Default data if this device supports emergency SUPL on non-DDS. + if (!mIsSuplDdsSwitchRequiredForEmergencyCall) { + Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not " + + "require DDS switch."); + return CompletableFuture.completedFuture(Boolean.TRUE); + } + + // Only override default data if we are IN_SERVICE already. + if (!isAvailableForEmergencyCalls(phone)) { + Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS"); + return CompletableFuture.completedFuture(Boolean.TRUE); + } + + // Only override default data if we are not roaming, we do not want to switch onto a network + // that only supports data plane only (if we do not know). + boolean isRoaming = phone.getServiceState().getVoiceRoaming(); + // In some roaming conditions, we know the roaming network doesn't support control plane + // fallback even though the home operator does. For these operators we will need to do a DDS + // switch anyway to make sure the SUPL request doesn't fail. + boolean roamingNetworkSupportsControlPlaneFallback = true; + String[] dataPlaneRoamPlmns = getConfig(phone.getSubId(), + CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY); + if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns) + .contains(phone.getServiceState().getOperatorNumeric())) { + roamingNetworkSupportsControlPlaneFallback = false; + } + if (isRoaming && roamingNetworkSupportsControlPlaneFallback) { + Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: roaming network is assumed " + + "to support CP fallback, not switching DDS."); + return CompletableFuture.completedFuture(Boolean.TRUE); + } + // Do not try to swap default data if we support CS fallback or it is assumed that the + // roaming network supports control plane fallback, we do not want to introduce a lag in + // emergency call setup time if possible. + final boolean supportsCpFallback = getConfig(phone.getSubId(), + CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, + CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY) + != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY; + if (supportsCpFallback && roamingNetworkSupportsControlPlaneFallback) { + Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier " + + "supports CP fallback."); + return CompletableFuture.completedFuture(Boolean.TRUE); + } + + // Get extension time, may be 0 for some carriers that support ECBM as well. Use + // CarrierConfig default if format fails. + int extensionTime = 0; + try { + extensionTime = Integer.parseInt(getConfig(phone.getSubId(), + CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0")); + } catch (NumberFormatException e) { + // Just use default. + } + CompletableFuture modemResultFuture = new CompletableFuture<>(); + try { + Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for " + + extensionTime + "seconds"); + mPhoneSwitcherProxy.getPhoneSwitcher().overrideDefaultDataForEmergency( + phone.getPhoneId(), extensionTime, modemResultFuture); + // Catch all exceptions, we want to continue with emergency call if possible. + } catch (Exception e) { + Rlog.w(TAG, + "possiblyOverrideDefaultDataForEmergencyCall: exception = " + e.getMessage()); + modemResultFuture = CompletableFuture.completedFuture(Boolean.FALSE); + } + return modemResultFuture; + } + + // Helper functions for easy CarrierConfigManager access + private String getConfig(int subId, String key, String defVal) { + return getConfigBundle(subId, key).getString(key, defVal); + } + private int getConfig(int subId, String key, int defVal) { + return getConfigBundle(subId, key).getInt(key, defVal); + } + private String[] getConfig(int subId, String key) { + return getConfigBundle(subId, key).getStringArray(key); + } + private boolean getConfig(int subId, String key, boolean defVal) { + return getConfigBundle(subId, key).getBoolean(key, defVal); + } + private PersistableBundle getConfigBundle(int subId, String key) { + if (mConfigManager == null) return new PersistableBundle(); + return mConfigManager.getConfigForSubId(subId, key); + } + + /** + * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only. + */ + private boolean isAvailableForEmergencyCalls(Phone phone) { + return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() + || phone.getServiceState().isEmergencyOnly(); + } + + /** + * Checks whether both {@code Phone}s are same or not. + */ + private static boolean isSamePhone(Phone p1, Phone p2) { + return p1 != null && p2 != null && (p1.getPhoneId() == p2.getPhoneId()); + } + + private static String emergencyTypeToString(@EmergencyType int emergencyType) { + switch (emergencyType) { + case EMERGENCY_TYPE_CALL: return "CALL"; + case EMERGENCY_TYPE_SMS: return "SMS"; + default: return "UNKNOWN"; + } + } +} diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..9c4ebfabe3cc09fe5a9c7402fe3ab0f4057b6825 --- /dev/null +++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.emergency; + +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.TelephonyManager; + +import com.android.internal.telephony.IIntegerConsumer; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.satellite.SatelliteController; +import com.android.telephony.Rlog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class that implements special behavior related to emergency calls or making phone calls + * when the radio is in the POWER_OFF STATE. Specifically, this class handles the case of the user + * trying to dial an emergency number while the radio is off (i.e. the device is in airplane mode) + * or a normal number while the radio is off (because of the device is on Bluetooth), by turning the + * radio back on, waiting for it to come up, and then retrying the call. + */ +public class RadioOnHelper implements RadioOnStateListener.Callback { + + private static final String TAG = "RadioOnStateListener"; + + private final Context mContext; + private RadioOnStateListener.Callback mCallback; + private List mListeners; + private List mInProgressListeners; + private boolean mIsRadioReady; + + public RadioOnHelper(Context context) { + mContext = context; + mInProgressListeners = new ArrayList<>(2); + } + + private void setupListeners() { + if (mListeners == null) { + mListeners = new ArrayList<>(2); + } + int activeModems = TelephonyManager.from(mContext).getActiveModemCount(); + // Add new listeners if active modem count increased. + while (mListeners.size() < activeModems) { + mListeners.add(new RadioOnStateListener()); + } + // Clean up listeners if active modem count decreased. + while (mListeners.size() > activeModems) { + mListeners.get(mListeners.size() - 1).cleanup(); + mListeners.remove(mListeners.size() - 1); + } + } + + /** + * Starts the "turn on radio" sequence. This is the (single) external API of the RadioOnHelper + * class. + * + * This method kicks off the following sequence: + * - Power on the radio for each Phone and disable the satellite modem + * - Listen for events telling us the radio has come up or the satellite modem is disabled. + * - Retry if we've gone a significant amount of time without any response. + * - Finally, clean up any leftover state. + * + * This method is safe to call from any thread, since it simply posts a message to the + * RadioOnHelper's handler (thus ensuring that the rest of the sequence is entirely serialized, + * and runs on the main looper.) + */ + public void triggerRadioOnAndListen(RadioOnStateListener.Callback callback, + boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber, + int emergencyTimeoutIntervalMillis) { + setupListeners(); + mCallback = callback; + mInProgressListeners.clear(); + mIsRadioReady = false; + for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount(); i++) { + Phone phone = PhoneFactory.getPhone(i); + if (phone == null) { + continue; + } + + int timeoutCallbackInterval = (forEmergencyCall && phone == phoneForEmergencyCall) + ? emergencyTimeoutIntervalMillis : 0; + mInProgressListeners.add(mListeners.get(i)); + mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall + && phone == phoneForEmergencyCall, timeoutCallbackInterval); + } + powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber); + if (SatelliteController.getInstance().isSatelliteEnabled()) { + powerOffSatellite(phoneForEmergencyCall); + } + } + + /** + * Attempt to power on the radio (i.e. take the device out of airplane mode). We'll eventually + * get an onServiceStateChanged() callback when the radio successfully comes up. + */ + private void powerOnRadio(boolean forEmergencyCall, Phone phoneForEmergencyCall, + boolean isTestEmergencyNumber) { + + // Always try to turn on the radio here independent of APM setting - if we got here in the + // first place, the radio is off independent of APM setting. + for (Phone phone : PhoneFactory.getPhones()) { + Rlog.d(TAG, "powerOnRadio, enabling Radio"); + if (isTestEmergencyNumber) { + phone.setRadioPowerOnForTestEmergencyCall(phone == phoneForEmergencyCall); + } else { + phone.setRadioPower(true, forEmergencyCall, phone == phoneForEmergencyCall, + false); + } + } + + // If airplane mode is on, we turn it off the same way that the Settings activity turns it + // off to keep the setting in sync. + if (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) > 0) { + Rlog.d(TAG, "==> Turning off airplane mode for emergency call."); + + // Change the system setting + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0); + + // Post the broadcast intend for change in airplane mode TODO: We really should not be + // in charge of sending this broadcast. If changing the setting is sufficient to trigger + // all of the rest of the logic, then that should also trigger the broadcast intent. + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", false); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + } + + /** + * Attempt to power off the satellite modem. We'll eventually get an + * onSatelliteModemStateChanged() callback when the satellite modem is successfully disabled. + */ + private void powerOffSatellite(Phone phoneForEmergencyCall) { + SatelliteController satelliteController = SatelliteController.getInstance(); + satelliteController.requestSatelliteEnabled(phoneForEmergencyCall.getSubId(), + false /* enableSatellite */, false /* enableDemoMode */, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + + } + }); + } + + /** + * This method is called from multiple Listeners on the Main Looper. Synchronization is not + * necessary. + */ + @Override + public void onComplete(RadioOnStateListener listener, boolean isRadioReady) { + mIsRadioReady |= isRadioReady; + mInProgressListeners.remove(listener); + if (mCallback != null && mInProgressListeners.isEmpty()) { + mCallback.onComplete(null, mIsRadioReady); + } + } + + @Override + public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) { + return (mCallback == null) + ? false : mCallback.isOkToCall(phone, serviceState, imsVoiceCapable); + } + + @Override + public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) { + return (mCallback == null) + ? false : mCallback.onTimeout(phone, serviceState, imsVoiceCapable); + } +} diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java new file mode 100644 index 0000000000000000000000000000000000000000..d61c146fbe8a76579276627105528bdc267e0b5c --- /dev/null +++ b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.emergency; + +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.satellite.ISatelliteStateCallback; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.SomeArgs; +import com.android.internal.telephony.IIntegerConsumer; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.satellite.SatelliteController; +import com.android.telephony.Rlog; + +import java.util.Locale; + +/** + * Helper class that listens to a Phone's radio state and sends an onComplete callback when we + * return true for isOkToCall. + */ +public class RadioOnStateListener { + + public interface Callback { + /** + * Receives the result of the RadioOnStateListener's attempt to turn on the radio + * and turn off the satellite modem. + */ + void onComplete(RadioOnStateListener listener, boolean isRadioReady); + + /** + * Returns whether or not this phone is ok to call. + * If it is, onComplete will be called shortly after. + * + * @param phone The Phone associated. + * @param serviceState The service state of that phone. + * @param imsVoiceCapable The IMS voice capability of that phone. + * @return {@code true} if this phone is ok to call. Otherwise, {@code false}. + */ + boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable); + + /** + * Returns whether or not this phone is ok to call. + * This callback will be called when timeout happens. + * If this returns {@code true}, onComplete will be called shortly after. + * Otherwise, a new timer will be started again to keep waiting for next timeout. + * The timeout interval will be passed to {@link #waitForRadioOn()} when registering + * this callback. + * + * @param phone The Phone associated. + * @param serviceState The service state of that phone. + * @param imsVoiceCapable The IMS voice capability of that phone. + * @return {@code true} if this phone is ok to call. Otherwise, {@code false}. + */ + boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable); + } + + private static final String TAG = "RadioOnStateListener"; + + // Number of times to retry the call, and time between retry attempts. + // not final for testing + private static int MAX_NUM_RETRIES = 5; + // not final for testing + private static long TIME_BETWEEN_RETRIES_MILLIS = 5000; // msec + + // Handler message codes; see handleMessage() + private static final int MSG_START_SEQUENCE = 1; + @VisibleForTesting + public static final int MSG_SERVICE_STATE_CHANGED = 2; + private static final int MSG_RETRY_TIMEOUT = 3; + @VisibleForTesting + public static final int MSG_RADIO_ON = 4; + public static final int MSG_RADIO_OFF_OR_NOT_AVAILABLE = 5; + public static final int MSG_IMS_CAPABILITY_CHANGED = 6; + public static final int MSG_TIMEOUT_ONTIMEOUT_CALLBACK = 7; + @VisibleForTesting + public static final int MSG_SATELLITE_ENABLED_CHANGED = 8; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_SEQUENCE: + SomeArgs args = (SomeArgs) msg.obj; + try { + Phone phone = (Phone) args.arg1; + RadioOnStateListener.Callback callback = + (RadioOnStateListener.Callback) args.arg2; + boolean forEmergencyCall = (boolean) args.arg3; + boolean isSelectedPhoneForEmergencyCall = (boolean) args.arg4; + int onTimeoutCallbackInterval = args.argi1; + startSequenceInternal(phone, callback, forEmergencyCall, + isSelectedPhoneForEmergencyCall, onTimeoutCallbackInterval); + } finally { + args.recycle(); + } + break; + case MSG_SERVICE_STATE_CHANGED: + onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result); + break; + case MSG_RADIO_ON: + onRadioOn(); + break; + case MSG_RADIO_OFF_OR_NOT_AVAILABLE: + registerForRadioOn(); + break; + case MSG_RETRY_TIMEOUT: + onRetryTimeout(); + break; + case MSG_IMS_CAPABILITY_CHANGED: + onImsCapabilityChanged(); + break; + case MSG_TIMEOUT_ONTIMEOUT_CALLBACK: + onTimeoutCallbackTimeout(); + break; + case MSG_SATELLITE_ENABLED_CHANGED: + onSatelliteEnabledChanged(); + break; + default: + Rlog.w(TAG, String.format(Locale.getDefault(), + "handleMessage: unexpected message: %d.", msg.what)); + break; + } + } + }; + + private final ISatelliteStateCallback mSatelliteCallback = new ISatelliteStateCallback.Stub() { + @Override + public void onSatelliteModemStateChanged(int state) { + mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED).sendToTarget(); + } + }; + + private Callback mCallback; // The callback to notify upon completion. + private Phone mPhone; // The phone that will attempt to place the call. + // SatelliteController instance to check whether satellite has been disabled. + private SatelliteController mSatelliteController; + private boolean mForEmergencyCall; // Whether radio is being turned on for emergency call. + // Whether this phone is selected to place emergency call. Can be true only if + // mForEmergencyCall is true. + private boolean mSelectedPhoneForEmergencyCall; + private int mNumRetriesSoFar; + private int mOnTimeoutCallbackInterval; // the interval between onTimeout callbacks + + /** + * Starts the "wait for radio" sequence. This is the (single) external API of the + * RadioOnStateListener class. + * + * This method kicks off the following sequence: + * - Listen for the service state change event telling us the radio has come up. + * - Listen for the satellite state changed event telling us the satellite service is disabled. + * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the + * radio. + * - Finally, clean up any leftover state. + * + * This method is safe to call from any thread, since it simply posts a message to the + * RadioOnStateListener's handler (thus ensuring that the rest of the sequence is entirely + * serialized, and runs only on the handler thread.) + */ + public void waitForRadioOn(Phone phone, Callback callback, + boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, + int onTimeoutCallbackInterval) { + Rlog.d(TAG, "waitForRadioOn: Phone " + phone.getPhoneId()); + + if (mPhone != null) { + // If there already is an ongoing request, ignore the new one! + return; + } + + SomeArgs args = SomeArgs.obtain(); + args.arg1 = phone; + args.arg2 = callback; + args.arg3 = forEmergencyCall; + args.arg4 = isSelectedPhoneForEmergencyCall; + args.argi1 = onTimeoutCallbackInterval; + mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget(); + } + + /** + * Actual implementation of waitForRadioOn(), guaranteed to run on the handler thread. + * + * @see #waitForRadioOn + */ + private void startSequenceInternal(Phone phone, Callback callback, + boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, + int onTimeoutCallbackInterval) { + Rlog.d(TAG, "startSequenceInternal: Phone " + phone.getPhoneId()); + mSatelliteController = SatelliteController.getInstance(); + + // First of all, clean up any state left over from a prior RadioOn call sequence. This + // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while + // we're already in the middle of the sequence. + cleanup(); + + mPhone = phone; + mCallback = callback; + mForEmergencyCall = forEmergencyCall; + mSelectedPhoneForEmergencyCall = isSelectedPhoneForEmergencyCall; + mOnTimeoutCallbackInterval = onTimeoutCallbackInterval; + + registerForServiceStateChanged(); + // Register for RADIO_OFF to handle cases where emergency call is dialed before + // we receive UNSOL_RESPONSE_RADIO_STATE_CHANGED with RADIO_OFF. + registerForRadioOff(); + if (mSatelliteController.isSatelliteEnabled()) { + // Register for satellite modem state changed to notify when satellite is disabled. + registerForSatelliteEnabledChanged(); + } + // Next step: when the SERVICE_STATE_CHANGED or SATELLITE_ENABLED_CHANGED event comes in, + // we'll retry the call; see onServiceStateChanged() and onSatelliteEnabledChanged(). + // But also, just in case, start a timer to make sure we'll retry the call even if the + // SERVICE_STATE_CHANGED or SATELLITE_ENABLED_CHANGED events never come in for some reason. + startRetryTimer(); + registerForImsCapabilityChanged(); + startOnTimeoutCallbackTimer(); + } + + private void onImsCapabilityChanged() { + if (mPhone == null) { + return; + } + + boolean imsVoiceCapable = mPhone.isVoiceOverCellularImsEnabled(); + + Rlog.d(TAG, String.format("onImsCapabilityChanged, capable = %s, Phone = %s", + imsVoiceCapable, mPhone.getPhoneId())); + + if (isOkToCall(mPhone.getServiceState().getState(), imsVoiceCapable)) { + Rlog.d(TAG, "onImsCapabilityChanged: ok to call!"); + + onComplete(true); + cleanup(); + } else { + // The IMS capability changed, but we're still not ready to call yet. + Rlog.d(TAG, "onImsCapabilityChanged: not ready to call yet, keep waiting."); + } + } + + private void onTimeoutCallbackTimeout() { + if (mPhone == null) { + return; + } + + if (onTimeout(mPhone.getServiceState().getState(), + mPhone.isVoiceOverCellularImsEnabled())) { + Rlog.d(TAG, "onTimeout: ok to call!"); + + onComplete(true); + cleanup(); + } else if (mNumRetriesSoFar > MAX_NUM_RETRIES) { + Rlog.w(TAG, "onTimeout: Hit MAX_NUM_RETRIES; giving up."); + cleanup(); + } else { + Rlog.d(TAG, "onTimeout: not ready to call yet, keep waiting."); + startOnTimeoutCallbackTimer(); + } + } + + /** + * Handles the SERVICE_STATE_CHANGED event. This event tells us that the radio state has changed + * and is probably coming up. We can now check to see if the conditions are met to place the + * call with {@link Callback#isOkToCall} + */ + private void onServiceStateChanged(ServiceState state) { + if (mPhone == null) { + return; + } + Rlog.d(TAG, String.format("onServiceStateChanged(), new state = %s, Phone = %s", state, + mPhone.getPhoneId())); + + // Possible service states: + // - STATE_IN_SERVICE // Normal operation + // - STATE_OUT_OF_SERVICE // Still searching for an operator to register to, + // // or no radio signal + // - STATE_EMERGENCY_ONLY // Only emergency numbers are allowed; currently not used + // - STATE_POWER_OFF // Radio is explicitly powered off (airplane mode) + + if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) { + // Woo hoo! It's OK to actually place the call. + Rlog.d(TAG, "onServiceStateChanged: ok to call!"); + + onComplete(true); + cleanup(); + } else { + // The service state changed, but we're still not ready to call yet. + Rlog.d(TAG, "onServiceStateChanged: not ready to call yet, keep waiting."); + } + } + + private void onRadioOn() { + if (mPhone == null) { + return; + } + ServiceState state = mPhone.getServiceState(); + Rlog.d(TAG, String.format("onRadioOn, state = %s, Phone = %s", state, mPhone.getPhoneId())); + if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) { + onComplete(true); + cleanup(); + } else { + Rlog.d(TAG, "onRadioOn: not ready to call yet, keep waiting."); + } + } + + private void onSatelliteEnabledChanged() { + if (mPhone == null) { + return; + } + if (isOkToCall(mPhone.getServiceState().getState(), + mPhone.isVoiceOverCellularImsEnabled())) { + onComplete(true); + cleanup(); + } else { + Rlog.d(TAG, "onSatelliteEnabledChanged: not ready to call yet, keep waiting."); + } + } + + /** + * Callback to see if it is okay to call yet, given the current conditions. + */ + private boolean isOkToCall(int serviceState, boolean imsVoiceCapable) { + return (mCallback == null) + ? false : mCallback.isOkToCall(mPhone, serviceState, imsVoiceCapable); + } + + /** + * Callback to see if it is okay to call yet, given the current conditions. + */ + private boolean onTimeout(int serviceState, boolean imsVoiceCapable) { + return (mCallback == null) + ? false : mCallback.onTimeout(mPhone, serviceState, imsVoiceCapable); + } + + /** + * Handles the retry timer expiring. + */ + private void onRetryTimeout() { + if (mPhone == null) { + return; + } + int serviceState = mPhone.getServiceState().getState(); + Rlog.d(TAG, + String.format(Locale.getDefault(), + "onRetryTimeout(): phone state = %s, service state = %d, retries = %d.", + mPhone.getState(), serviceState, mNumRetriesSoFar)); + + // - If we're actually in a call, we've succeeded. + // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode + // but somehow didn't get the service state change event. In that case, try to place the + // call. + // - If the radio is still powered off, try powering it on again. + + if (isOkToCall(serviceState, mPhone.isVoiceOverCellularImsEnabled())) { + Rlog.d(TAG, "onRetryTimeout: Radio is on. Cleaning up."); + + // Woo hoo -- we successfully got out of airplane mode. + onComplete(true); + cleanup(); + } else { + // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not + // powered-on. Try again. + + mNumRetriesSoFar++; + Rlog.d(TAG, "mNumRetriesSoFar is now " + mNumRetriesSoFar); + + if (mNumRetriesSoFar > MAX_NUM_RETRIES) { + if (mHandler.hasMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK)) { + Rlog.w(TAG, "Hit MAX_NUM_RETRIES; waiting onTimeout callback"); + return; + } + Rlog.w(TAG, "Hit MAX_NUM_RETRIES; giving up."); + cleanup(); + } else { + Rlog.d(TAG, "Trying (again) to turn the radio on and satellite modem off."); + mPhone.setRadioPower(true, mForEmergencyCall, mSelectedPhoneForEmergencyCall, + false); + if (mSatelliteController.isSatelliteEnabled()) { + mSatelliteController.requestSatelliteEnabled(mPhone.getSubId(), + false /* enableSatellite */, false /* enableDemoMode */, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED) + .sendToTarget(); + } + }); + } + startRetryTimer(); + } + } + } + + /** + * Clean up when done with the whole sequence: either after successfully turning on the radio, + * or after bailing out because of too many failures. + * + * The exact cleanup steps are: + * - Notify callback if we still hadn't sent it a response. + * - Double-check that we're not still registered for any telephony events + * - Clean up any extraneous handler messages (like retry timeouts) still in the queue + * + * Basically this method guarantees that there will be no more activity from the + * RadioOnStateListener until someone kicks off the whole sequence again with another call to + * {@link #waitForRadioOn} + * + * TODO: Do the work for the comment below: Note we don't call this method simply after a + * successful call to placeCall(), since it's still possible the call will disconnect very + * quickly with an OUT_OF_SERVICE error. + */ + public void cleanup() { + Rlog.d(TAG, "cleanup()"); + + // This will send a failure call back if callback has yet to be invoked. If the callback was + // already invoked, it's a no-op. + onComplete(false); + + unregisterForServiceStateChanged(); + unregisterForRadioOff(); + unregisterForRadioOn(); + unregisterForSatelliteEnabledChanged(); + cancelRetryTimer(); + unregisterForImsCapabilityChanged(); + + // Used for unregisterForServiceStateChanged() so we null it out here instead. + mPhone = null; + mNumRetriesSoFar = 0; + mOnTimeoutCallbackInterval = 0; + } + + private void startRetryTimer() { + cancelRetryTimer(); + mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS); + } + + private void cancelRetryTimer() { + mHandler.removeMessages(MSG_RETRY_TIMEOUT); + } + + private void registerForServiceStateChanged() { + // Unregister first, just to make sure we never register ourselves twice. (We need this + // because Phone.registerForServiceStateChanged() does not prevent multiple registration of + // the same handler.) + unregisterForServiceStateChanged(); + mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null); + } + + private void unregisterForServiceStateChanged() { + // This method is safe to call even if we haven't set mPhone yet. + if (mPhone != null) { + mPhone.unregisterForServiceStateChanged(mHandler); // Safe even if unnecessary + } + mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED); // Clean up any pending messages too + } + + private void registerForRadioOff() { + mPhone.mCi.registerForOffOrNotAvailable(mHandler, MSG_RADIO_OFF_OR_NOT_AVAILABLE, null); + } + + private void unregisterForRadioOff() { + // This method is safe to call even if we haven't set mPhone yet. + if (mPhone != null) { + mPhone.mCi.unregisterForOffOrNotAvailable(mHandler); // Safe even if unnecessary + } + mHandler.removeMessages(MSG_RADIO_OFF_OR_NOT_AVAILABLE); // Clean up any pending messages + } + + private void registerForRadioOn() { + unregisterForRadioOff(); + mPhone.mCi.registerForOn(mHandler, MSG_RADIO_ON, null); + } + + private void unregisterForRadioOn() { + // This method is safe to call even if we haven't set mPhone yet. + if (mPhone != null) { + mPhone.mCi.unregisterForOn(mHandler); // Safe even if unnecessary + } + mHandler.removeMessages(MSG_RADIO_ON); // Clean up any pending messages too + } + + private void registerForSatelliteEnabledChanged() { + mSatelliteController.registerForSatelliteModemStateChanged( + mPhone.getSubId(), mSatelliteCallback); + } + + private void unregisterForSatelliteEnabledChanged() { + int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + if (mPhone != null) { + subId = mPhone.getSubId(); + } + mSatelliteController.unregisterForSatelliteModemStateChanged(subId, mSatelliteCallback); + mHandler.removeMessages(MSG_SATELLITE_ENABLED_CHANGED); + } + + private void registerForImsCapabilityChanged() { + unregisterForImsCapabilityChanged(); + mPhone.getServiceStateTracker() + .registerForImsCapabilityChanged(mHandler, MSG_IMS_CAPABILITY_CHANGED, null); + } + + private void unregisterForImsCapabilityChanged() { + if (mPhone != null) { + mPhone.getServiceStateTracker() + .unregisterForImsCapabilityChanged(mHandler); + } + mHandler.removeMessages(MSG_IMS_CAPABILITY_CHANGED); + } + + private void startOnTimeoutCallbackTimer() { + Rlog.d(TAG, "startOnTimeoutCallbackTimer: mOnTimeoutCallbackInterval=" + + mOnTimeoutCallbackInterval); + mHandler.removeMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK); + if (mOnTimeoutCallbackInterval > 0) { + mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT_ONTIMEOUT_CALLBACK, + mOnTimeoutCallbackInterval); + } + } + + private void onComplete(boolean isRadioReady) { + if (mCallback != null) { + Callback tempCallback = mCallback; + mCallback = null; + tempCallback.onComplete(this, isRadioReady); + } + } + + @VisibleForTesting + public Handler getHandler() { + return mHandler; + } + + @VisibleForTesting + public void setMaxNumRetries(int retries) { + MAX_NUM_RETRIES = retries; + } + + @VisibleForTesting + public void setTimeBetweenRetriesMillis(long timeMs) { + TIME_BETWEEN_RETRIES_MILLIS = timeMs; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || !getClass().equals(o.getClass())) + return false; + + RadioOnStateListener that = (RadioOnStateListener) o; + + if (mNumRetriesSoFar != that.mNumRetriesSoFar) { + return false; + } + if (mCallback != null ? !mCallback.equals(that.mCallback) : that.mCallback != null) { + return false; + } + return mPhone != null ? mPhone.equals(that.mPhone) : that.mPhone == null; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + mNumRetriesSoFar; + hash = 31 * hash + (mCallback == null ? 0 : mCallback.hashCode()); + hash = 31 * hash + (mPhone == null ? 0 : mPhone.hashCode()); + return hash; + } +} diff --git a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java index bb42b2ac4a5bd85e3f7d19e58c67bf188bf4606b..2f73c916c68004903b37b3ce653ea6ea12b2a379 100644 --- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java +++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java @@ -39,8 +39,6 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.UiccCard; import com.android.internal.telephony.uicc.UiccController; @@ -416,7 +414,7 @@ public class EuiccCardController extends IEuiccCardController.Stub { // if there is no iccid enabled on this port, return null. if (TextUtils.isEmpty(iccId)) { try { - callback.onComplete(EuiccCardManager.RESULT_PROFILE_NOT_FOUND, null); + callback.onComplete(EuiccCardManager.RESULT_PROFILE_DOES_NOT_EXIST, null); } catch (RemoteException exception) { loge("getEnabledProfile callback failure.", exception); } @@ -652,14 +650,8 @@ public class EuiccCardController extends IEuiccCardController.Stub { @Override public void onResult(Void result) { Log.i(TAG, "Request subscription info list refresh after delete."); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( - List.of(mUiccController.convertToPublicCardId(cardId)), null); - } else { - SubscriptionController.getInstance() - .requestEmbeddedSubscriptionInfoListRefresh( - mUiccController.convertToPublicCardId(cardId)); - } + SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( + List.of(mUiccController.convertToPublicCardId(cardId)), null); try { callback.onComplete(EuiccCardManager.RESULT_OK); } catch (RemoteException exception) { @@ -709,14 +701,8 @@ public class EuiccCardController extends IEuiccCardController.Stub { @Override public void onResult(Void result) { Log.i(TAG, "Request subscription info list refresh after reset memory."); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( - List.of(mUiccController.convertToPublicCardId(cardId)), null); - } else { - SubscriptionController.getInstance() - .requestEmbeddedSubscriptionInfoListRefresh( - mUiccController.convertToPublicCardId(cardId)); - } + SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( + List.of(mUiccController.convertToPublicCardId(cardId)), null); try { callback.onComplete(EuiccCardManager.RESULT_OK); } catch (RemoteException exception) { @@ -1203,14 +1189,8 @@ public class EuiccCardController extends IEuiccCardController.Stub { @Override public void onResult(byte[] result) { Log.i(TAG, "Request subscription info list refresh after install."); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( - List.of(mUiccController.convertToPublicCardId(cardId)), null); - } else { - SubscriptionController.getInstance() - .requestEmbeddedSubscriptionInfoListRefresh( - mUiccController.convertToPublicCardId(cardId)); - } + SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( + List.of(mUiccController.convertToPublicCardId(cardId)), null); try { callback.onComplete(EuiccCardManager.RESULT_OK, result); } catch (RemoteException exception) { diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java index 974acf9a63a438324e308be87a93529a16722631..c417a34c545e5c7651be78d18a8b4bb5ed86e210 100644 --- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java +++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java @@ -101,7 +101,8 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { * true or onServiceDisconnected is called (and no package change has occurred which should * force us to reestablish the binding). */ - private static final int BIND_TIMEOUT_MILLIS = 30000; + @VisibleForTesting + static final int BIND_TIMEOUT_MILLIS = 30000; /** * Maximum amount of idle time to hold the binding while in {@link ConnectedState}. After this, @@ -225,6 +226,8 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { static class GetMetadataRequest { DownloadableSubscription mSubscription; boolean mForceDeactivateSim; + boolean mSwitchAfterDownload; + int mPortIndex; GetMetadataCommandCallback mCallback; } @@ -389,6 +392,9 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { mSm = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + // TODO(b/239277548): Disable debug logging after analysing this bug. + setDbg(true); + // Unavailable/Available both monitor for package changes and update mSelectedComponent but // do not need to adjust the binding. mUnavailableState = new UnavailableState(); @@ -444,13 +450,15 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { /** Asynchronously fetch metadata for the given downloadable subscription. */ @VisibleForTesting(visibility = PACKAGE) - public void getDownloadableSubscriptionMetadata(int cardId, - DownloadableSubscription subscription, + public void getDownloadableSubscriptionMetadata(int cardId, int portIndex, + DownloadableSubscription subscription, boolean switchAfterDownload, boolean forceDeactivateSim, GetMetadataCommandCallback callback) { GetMetadataRequest request = new GetMetadataRequest(); request.mSubscription = subscription; request.mForceDeactivateSim = forceDeactivateSim; + request.mSwitchAfterDownload = switchAfterDownload; + request.mPortIndex = portIndex; request.mCallback = callback; sendMessage(CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA, cardId, 0 /* arg2 */, request); } @@ -549,6 +557,11 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { callback); } + @VisibleForTesting + public final IEuiccService getBinder() { + return mEuiccService; + } + /** * State in which no EuiccService is available. * @@ -686,6 +699,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { } return HANDLED; } else if (message.what == CMD_CONNECT_TIMEOUT) { + unbind(); transitionTo(mAvailableState); return HANDLED; } else if (isEuiccCommand(message.what)) { @@ -749,7 +763,9 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: { GetMetadataRequest request = (GetMetadataRequest) message.obj; mEuiccService.getDownloadableSubscriptionMetadata(slotId, + request.mPortIndex, request.mSubscription, + request.mSwitchAfterDownload, request.mForceDeactivateSim, new IGetDownloadableSubscriptionMetadataCallback.Stub() { @Override @@ -1057,9 +1073,8 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { for (int slotIndex = 0; slotIndex < slotInfos.length; slotIndex++) { // Report Anomaly in case UiccSlotInfo is not. if (slotInfos[slotIndex] == null) { - AnomalyReporter.reportAnomaly( - UUID.fromString("4195b83d-6cee-4999-a02f-d0b9f7079b9d"), - "EuiccConnector: Found UiccSlotInfo Null object."); + Log.i(TAG, "No UiccSlotInfo found for slotIndex: " + slotIndex); + return SubscriptionManager.INVALID_SIM_SLOT_INDEX; } String retrievedCardId = slotInfos[slotIndex] != null ? slotInfos[slotIndex].getCardId() : null; diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java index 294299accd1ab1b93d96c900ca7ce1d6daf97298..a5b95c35bf867128ea925632187e8380ca68388d 100644 --- a/src/java/com/android/internal/telephony/euicc/EuiccController.java +++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java @@ -59,7 +59,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CarrierPrivilegesTracker; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.IccUtils; @@ -386,6 +385,7 @@ public class EuiccController extends IEuiccController.Stub { void getDownloadableSubscriptionMetadata(int cardId, DownloadableSubscription subscription, boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) { + Log.d(TAG, " getDownloadableSubscriptionMetadata callingPackage: " + callingPackage); if (!callerCanWriteEmbeddedSubscriptions()) { throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata"); } @@ -393,7 +393,8 @@ public class EuiccController extends IEuiccController.Stub { long token = Binder.clearCallingIdentity(); try { mConnector.getDownloadableSubscriptionMetadata(cardId, - subscription, forceDeactivateSim, + TelephonyManager.DEFAULT_PORT_INDEX, subscription, + false /* switchAfterDownload */, forceDeactivateSim, new GetMetadataCommandCallback( token, subscription, callingPackage, callbackIntent)); } finally { @@ -602,8 +603,8 @@ public class EuiccController extends IEuiccController.Stub { if (!isConsentNeededToResolvePortIndex && canManageSubscriptionOnTargetSim(cardId, callingPackage, true, portIndex)) { - mConnector.getDownloadableSubscriptionMetadata(cardId, subscription, - forceDeactivateSim, + mConnector.getDownloadableSubscriptionMetadata(cardId, portIndex, + subscription, switchAfterDownload, forceDeactivateSim, new DownloadSubscriptionGetMetadataCommandCallback(token, subscription, switchAfterDownload, callingPackage, forceDeactivateSim, callbackIntent, false /* withUserConsent */, portIndex)); @@ -714,7 +715,8 @@ public class EuiccController extends IEuiccController.Stub { Log.d(TAG, " downloadSubscriptionPrivilegedCheckMetadata cardId: " + cardId + " switchAfterDownload: " + switchAfterDownload + " portIndex: " + portIndex + " forceDeactivateSim: " + forceDeactivateSim); - mConnector.getDownloadableSubscriptionMetadata(cardId, subscription, forceDeactivateSim, + mConnector.getDownloadableSubscriptionMetadata(cardId, portIndex, + subscription, switchAfterDownload, forceDeactivateSim, new DownloadSubscriptionGetMetadataCommandCallback(callingToken, subscription, switchAfterDownload, callingPackage, forceDeactivateSim, callbackIntent, true /* withUserConsent */, portIndex)); @@ -863,6 +865,7 @@ public class EuiccController extends IEuiccController.Stub { void getDefaultDownloadableSubscriptionList(int cardId, boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) { + Log.d(TAG, " getDefaultDownloadableSubscriptionList callingPackage: " + callingPackage); if (!callerCanWriteEmbeddedSubscriptions()) { throw new SecurityException( "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get default list"); @@ -1148,7 +1151,8 @@ public class EuiccController extends IEuiccController.Stub { * Returns the resolved portIndex or {@link TelephonyManager#INVALID_PORT_INDEX} if calling * cannot manage any active subscription. */ - private int getResolvedPortIndexForDisableSubscription(int cardId, String callingPackage, + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public int getResolvedPortIndexForDisableSubscription(int cardId, String callingPackage, boolean callerCanWriteEmbeddedSubscriptions) { List subInfoList = mSubscriptionManager .getActiveSubscriptionInfoList(/* userVisibleOnly */false); @@ -1176,7 +1180,8 @@ public class EuiccController extends IEuiccController.Stub { * Returns the resolved portIndex or {@link TelephonyManager#INVALID_PORT_INDEX} if no port * is available without user consent. */ - private int getResolvedPortIndexForSubscriptionSwitch(int cardId) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public int getResolvedPortIndexForSubscriptionSwitch(int cardId) { int slotIndex = getSlotIndexFromCardId(cardId); // Euicc Slot UiccSlot slot = UiccController.getInstance().getUiccSlot(slotIndex); @@ -1586,15 +1591,9 @@ public class EuiccController extends IEuiccController.Stub { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void refreshSubscriptionsAndSendResult( PendingIntent callbackIntent, int resultCode, Intent extrasIntent) { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( - List.of(mTelephonyManager.getCardIdForDefaultEuicc()), - () -> sendResult(callbackIntent, resultCode, extrasIntent)); - } else { - SubscriptionController.getInstance() - .requestEmbeddedSubscriptionInfoListRefresh( - () -> sendResult(callbackIntent, resultCode, extrasIntent)); - } + SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions( + List.of(mTelephonyManager.getCardIdForDefaultEuicc()), + () -> sendResult(callbackIntent, resultCode, extrasIntent)); } /** Dispatch the given callback intent with the given result code and data. */ @@ -1888,8 +1887,6 @@ public class EuiccController extends IEuiccController.Stub { boolean hasActiveEmbeddedSubscription = subInfoList.stream().anyMatch( subInfo -> subInfo.isEmbedded() && subInfo.getCardId() == cardId && (!usePortIndex || subInfo.getPortIndex() == targetPortIndex)); - Log.d(TAG, "canManageSubscriptionOnTargetSim hasActiveEmbeddedSubscriptions: " - + hasActiveEmbeddedSubscription); if (hasActiveEmbeddedSubscription) { // hasActiveEmbeddedSubscription is true if there is an active embedded subscription // on the target port(in case of usePortIndex is true) or if there is an active @@ -1946,59 +1943,84 @@ public class EuiccController extends IEuiccController.Stub { @Override public boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage) { - List cardInfos; + mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); + // If calling app is targeted for Android U and beyond, check for other conditions + // to decide the port availability. + boolean shouldCheckConditionsForInactivePort = isCompatChangeEnabled(callingPackage, + EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK); + // In the event that this check is coming from ONS, WRITE_EMBEDDED_SUBSCRIPTIONS will be + // required for the case where a port is inactive but could trivially be enabled without + // requiring user consent. + boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions(); final long token = Binder.clearCallingIdentity(); try { - cardInfos = mTelephonyManager.getUiccCardsInfo(); - } finally { - Binder.restoreCallingIdentity(token); - } - for (UiccCardInfo info : cardInfos) { - if (info == null || info.getCardId() != cardId) { - continue; - } - // Return false in case of non esim or passed port index is greater than - // the available ports. - if (!info.isEuicc() || (portIndex == TelephonyManager.INVALID_PORT_INDEX) - || portIndex >= info.getPorts().size()) { - return false; - } - for (UiccPortInfo portInfo : info.getPorts()) { - if (portInfo == null || portInfo.getPortIndex() != portIndex) { + List cardInfos = mTelephonyManager.getUiccCardsInfo(); + for (UiccCardInfo info : cardInfos) { + if (info == null || info.getCardId() != cardId) { continue; } - // Return false if port is not active. - if (!portInfo.isActive()) { - return false; - } - // A port is available if it has no profiles enabled on it or calling app has - // Carrier privilege over the profile installed on the selected port. - if (TextUtils.isEmpty(portInfo.getIccId())) { - return true; - } - UiccPort uiccPort = - UiccController.getInstance().getUiccPortForSlot( - info.getPhysicalSlotIndex(), portIndex); - // Some eSim Vendors return boot profile iccid if no profile is installed. - // So in this case if profile is empty, port is available. - if (uiccPort != null - && uiccPort.getUiccProfile() != null - && uiccPort.getUiccProfile().isEmptyProfile()) { - return true; - } - Phone phone = PhoneFactory.getPhone(portInfo.getLogicalSlotIndex()); - if (phone == null) { - Log.e(TAG, "Invalid logical slot: " + portInfo.getLogicalSlotIndex()); + // Return false in case of non esim or passed port index is greater than + // the available ports. + if (!info.isEuicc() || (portIndex == TelephonyManager.INVALID_PORT_INDEX) + || portIndex >= info.getPorts().size()) { return false; } - CarrierPrivilegesTracker cpt = phone.getCarrierPrivilegesTracker(); - if (cpt == null) { - Log.e(TAG, "No CarrierPrivilegesTracker"); - return false; + for (UiccPortInfo portInfo : info.getPorts()) { + if (portInfo == null || portInfo.getPortIndex() != portIndex) { + continue; + } + if (!portInfo.isActive()) { + // port is inactive, check whether the caller can activate a new profile + // seamlessly. This is possible in below condition: + // 1. Device in DSDS Mode(P+E). + // 2. pSIM slot is active but no active subscription. + // 3. Caller has carrier privileges on any phone or has + // WRITE_EMBEDDED_SUBSCRIPTIONS. The latter covers calls from ONS + // which does not have carrier privileges. + if (!shouldCheckConditionsForInactivePort) { + return false; + } + boolean hasActiveRemovableNonEuiccSlot = getRemovableNonEuiccSlot() != null + && getRemovableNonEuiccSlot().isActive(); + boolean hasCarrierPrivileges = mTelephonyManager + .checkCarrierPrivilegesForPackageAnyPhone(callingPackage) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + return mTelephonyManager.isMultiSimEnabled() + && hasActiveRemovableNonEuiccSlot + && !isRemovalNonEuiccSlotHasActiveSubscription() + && (hasCarrierPrivileges || callerCanWriteEmbeddedSubscriptions); + } + // A port is available if it has no profiles enabled on it or calling app has + // Carrier privilege over the profile installed on the selected port. + if (TextUtils.isEmpty(portInfo.getIccId())) { + return true; + } + UiccPort uiccPort = + UiccController.getInstance().getUiccPortForSlot( + info.getPhysicalSlotIndex(), portIndex); + // Some eSim Vendors return boot profile iccid if no profile is installed. + // So in this case if profile is empty, port is available. + if (uiccPort != null + && uiccPort.getUiccProfile() != null + && uiccPort.getUiccProfile().isEmptyProfile()) { + return true; + } + Phone phone = PhoneFactory.getPhone(portInfo.getLogicalSlotIndex()); + if (phone == null) { + Log.e(TAG, "Invalid logical slot: " + portInfo.getLogicalSlotIndex()); + return false; + } + CarrierPrivilegesTracker cpt = phone.getCarrierPrivilegesTracker(); + if (cpt == null) { + Log.e(TAG, "No CarrierPrivilegesTracker"); + return false; + } + return (cpt.getCarrierPrivilegeStatusForPackage(callingPackage) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS); } - return (cpt.getCarrierPrivilegeStatusForPackage(callingPackage) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS); } + } finally { + Binder.restoreCallingIdentity(token); } return false; } diff --git a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java index 0abd4ab7115d7326bce76221880f25f130fa2d7c..907f1586a7fa9e8a99bedcc2bc5194d003f5f2ae 100644 --- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java +++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java @@ -24,10 +24,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.AsyncResult; import android.os.Build; +import android.os.Looper; import android.os.Message; import android.os.SystemProperties; import android.provider.Telephony.Sms.Intents; +import com.android.ims.ImsManager; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.InboundSmsHandler; import com.android.internal.telephony.Phone; @@ -43,7 +45,7 @@ import com.android.internal.telephony.uicc.UsimServiceTable; */ public class GsmInboundSmsHandler extends InboundSmsHandler { - private static BroadcastReceiver sTestBroadcastReceiver; + private BroadcastReceiver mTestBroadcastReceiver; /** Handler for SMS-PP data download messages to UICC. */ private final UsimDataDownloadHandler mDataDownloadHandler; @@ -56,18 +58,18 @@ public class GsmInboundSmsHandler extends InboundSmsHandler { * Create a new GSM inbound SMS handler. */ private GsmInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, - Phone phone) { - super("GsmInboundSmsHandler", context, storageMonitor, phone); + Phone phone, Looper looper) { + super("GsmInboundSmsHandler", context, storageMonitor, phone, looper); phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null); mDataDownloadHandler = new UsimDataDownloadHandler(phone.mCi, phone.getPhoneId()); mCellBroadcastServiceManager.enable(); if (TEST_MODE) { - if (sTestBroadcastReceiver == null) { - sTestBroadcastReceiver = new GsmCbTestBroadcastReceiver(); + if (mTestBroadcastReceiver == null) { + mTestBroadcastReceiver = new GsmCbTestBroadcastReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(TEST_ACTION); - context.registerReceiver(sTestBroadcastReceiver, filter, + context.registerReceiver(mTestBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); } } @@ -127,8 +129,9 @@ public class GsmInboundSmsHandler extends InboundSmsHandler { * Wait for state machine to enter startup state. We can't send any messages until then. */ public static GsmInboundSmsHandler makeInboundSmsHandler(Context context, - SmsStorageMonitor storageMonitor, Phone phone) { - GsmInboundSmsHandler handler = new GsmInboundSmsHandler(context, storageMonitor, phone); + SmsStorageMonitor storageMonitor, Phone phone, Looper looper) { + GsmInboundSmsHandler handler = + new GsmInboundSmsHandler(context, storageMonitor, phone, looper); handler.start(); return handler; } @@ -153,7 +156,8 @@ public class GsmInboundSmsHandler extends InboundSmsHandler { * or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC */ @Override - protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource) { + protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource, + int token) { SmsMessage sms = (SmsMessage) smsb; if (sms.isTypeZero()) { @@ -177,7 +181,7 @@ public class GsmInboundSmsHandler extends InboundSmsHandler { // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1. if (sms.isUsimDataDownload()) { UsimServiceTable ust = mPhone.getUsimServiceTable(); - return mDataDownloadHandler.handleUsimDataDownload(ust, sms, smsSource); + return mDataDownloadHandler.handleUsimDataDownload(ust, sms, smsSource, token); } boolean handled = false; @@ -268,4 +272,15 @@ public class GsmInboundSmsHandler extends InboundSmsHandler { android.telephony.SmsMessage.FORMAT_3GPP); mPhone.getSmsStats().onIncomingSmsVoicemail(false /* is3gpp2 */, smsSource); } + + /** + * sets ImsManager object. + */ + public boolean setImsManager(ImsManager imsManager) { + if (mDataDownloadHandler != null) { + mDataDownloadHandler.setImsManager(imsManager); + return true; + } + return false; + } } diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java index 7f5b60761dccbdba286367c91fabc5678285dc48..9de3ee9e697dfc58b98b8a946ee0c24a1469d872 100644 --- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java +++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java @@ -49,6 +49,7 @@ import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CallForwardInfo; import com.android.internal.telephony.CallStateException; +import com.android.internal.telephony.CallWaitingController; import com.android.internal.telephony.CommandException; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.GsmCdmaPhone; @@ -1168,11 +1169,25 @@ public final class GsmMmiCode extends Handler implements MmiCode { int serviceClass = siToServiceClass(mSia); if (isActivate() || isDeactivate()) { + if (serviceClass == SERVICE_CLASS_NONE + || (serviceClass & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE) { + if (mPhone.getTerminalBasedCallWaitingState(true) + != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) { + mPhone.setCallWaiting(isActivate(), serviceClass, + obtainMessage(EVENT_SET_COMPLETE, this)); + return; + } + } mPhone.mCi.setCallWaiting(isActivate(), serviceClass, obtainMessage(EVENT_SET_COMPLETE, this)); } else if (isInterrogate()) { - mPhone.mCi.queryCallWaiting(serviceClass, - obtainMessage(EVENT_QUERY_COMPLETE, this)); + if (mPhone.getTerminalBasedCallWaitingState(true) + != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) { + mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this)); + } else { + mPhone.mCi.queryCallWaiting(serviceClass, + obtainMessage(EVENT_QUERY_COMPLETE, this)); + } } else { throw new RuntimeException ("Invalid or Unsupported MMI Code"); } diff --git a/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java index ed819c1adca025a21cd5f22998687bbec34e087e..bae56d127c0431e654e4bc822145ae57911fe7dd 100644 --- a/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java +++ b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java @@ -17,13 +17,19 @@ package com.android.internal.telephony.gsm; import android.app.Activity; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; import android.os.AsyncResult; import android.os.Handler; import android.os.Message; import android.provider.Telephony.Sms.Intents; import android.telephony.PhoneNumberUtils; import android.telephony.SmsManager; +import android.telephony.ims.stub.ImsSmsImplBase; +import com.android.ims.ImsException; +import com.android.ims.ImsManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.InboundSmsHandler; import com.android.internal.telephony.PhoneFactory; @@ -61,10 +67,14 @@ public class UsimDataDownloadHandler extends Handler { private final CommandsInterface mCi; private final int mPhoneId; + private ImsManager mImsManager; + Resources mResource; public UsimDataDownloadHandler(CommandsInterface commandsInterface, int phoneId) { mCi = commandsInterface; mPhoneId = phoneId; + mImsManager = null; // will get initialized when ImsManager connection is ready + mResource = Resources.getSystem(); } /** @@ -79,7 +89,7 @@ public class UsimDataDownloadHandler extends Handler { * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure */ int handleUsimDataDownload(UsimServiceTable ust, SmsMessage smsMessage, - @InboundSmsHandler.SmsSource int smsSource) { + @InboundSmsHandler.SmsSource int smsSource, int token) { // If we receive an SMS-PP message before the UsimServiceTable has been loaded, // assume that the data download service is not present. This is very unlikely to // happen because the IMS connection will not be established until after the ISIM @@ -87,7 +97,7 @@ public class UsimDataDownloadHandler extends Handler { if (ust != null && ust.isAvailable( UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) { Rlog.d(TAG, "Received SMS-PP data download, sending to UICC."); - return startDataDownload(smsMessage, smsSource); + return startDataDownload(smsMessage, smsSource, token); } else { Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC."); String smsc = IccUtils.bytesToHexString( @@ -95,7 +105,8 @@ public class UsimDataDownloadHandler extends Handler { smsMessage.getServiceCenterAddress())); mCi.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc, IccUtils.bytesToHexString(smsMessage.getPdu()), - obtainMessage(EVENT_WRITE_SMS_COMPLETE)); + obtainMessage(EVENT_WRITE_SMS_COMPLETE, + new int[]{ smsSource, smsMessage.mMessageRef, token })); addUsimDataDownloadToMetrics(false, smsSource); return Activity.RESULT_OK; // acknowledge after response from write to USIM } @@ -111,9 +122,9 @@ public class UsimDataDownloadHandler extends Handler { * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure */ public int startDataDownload(SmsMessage smsMessage, - @InboundSmsHandler.SmsSource int smsSource) { + @InboundSmsHandler.SmsSource int smsSource, int token) { if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD, - smsSource, 0 /* unused */, smsMessage))) { + smsSource, token, smsMessage))) { return Activity.RESULT_OK; // we will send SMS ACK/ERROR based on UICC response } else { Rlog.e(TAG, "startDataDownload failed to send message to start data download."); @@ -122,7 +133,7 @@ public class UsimDataDownloadHandler extends Handler { } private void handleDataDownload(SmsMessage smsMessage, - @InboundSmsHandler.SmsSource int smsSource) { + @InboundSmsHandler.SmsSource int smsSource, int token) { int dcs = smsMessage.getDataCodingScheme(); int pid = smsMessage.getProtocolIdentifier(); byte[] pdu = smsMessage.getPdu(); // includes SC address @@ -139,6 +150,7 @@ public class UsimDataDownloadHandler extends Handler { byte[] envelope = new byte[totalLength]; int index = 0; + Rlog.d(TAG, "smsSource: " + smsSource + "Token: " + token); // SMS-PP download tag and length (assumed to be < 256 bytes). envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG; @@ -173,14 +185,16 @@ public class UsimDataDownloadHandler extends Handler { // Verify that we calculated the payload size correctly. if (index != envelope.length) { Rlog.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting."); - acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR); + acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, + smsSource, token, smsMessage.mMessageRef); addUsimDataDownloadToMetrics(false, smsSource); return; } String encodedEnvelope = IccUtils.bytesToHexString(envelope); mCi.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage( - EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid })); + EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid, smsSource, + smsMessage.mMessageRef, token })); addUsimDataDownloadToMetrics(true, smsSource); } @@ -211,7 +225,8 @@ public class UsimDataDownloadHandler extends Handler { * @param response UICC response encoded as hexadecimal digits. First two bytes are the * UICC SW1 and SW2 status bytes. */ - private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid) { + private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid, + int smsSource, int token, int messageRef) { int sw1 = response.sw1; int sw2 = response.sw2; @@ -221,7 +236,8 @@ public class UsimDataDownloadHandler extends Handler { success = true; } else if (sw1 == 0x93 && sw2 == 0x00) { Rlog.e(TAG, "USIM data download failed: Toolkit busy"); - acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY); + acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY, + smsSource, token, messageRef); return; } else if (sw1 == 0x62 || sw1 == 0x63) { Rlog.e(TAG, "USIM data download failed: " + response.toString()); @@ -234,10 +250,11 @@ public class UsimDataDownloadHandler extends Handler { byte[] responseBytes = response.payload; if (responseBytes == null || responseBytes.length == 0) { if (success) { - mCi.acknowledgeLastIncomingGsmSms(true, 0, null); + acknowledgeSmsWithSuccess(0, smsSource, token, messageRef); } else { acknowledgeSmsWithError( - CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR); + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, smsSource, + token, messageRef); } return; } @@ -268,12 +285,32 @@ public class UsimDataDownloadHandler extends Handler { System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length); - mCi.acknowledgeIncomingGsmSmsWithPdu(success, - IccUtils.bytesToHexString(smsAckPdu), null); + if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) { + acknowledgeImsSms(token, messageRef, true, smsAckPdu); + } else { + mCi.acknowledgeIncomingGsmSmsWithPdu(success, + IccUtils.bytesToHexString(smsAckPdu), null); + } + } + + private void acknowledgeSmsWithSuccess(int cause, int smsSource, int token, int messageRef) { + Rlog.d(TAG, "acknowledgeSmsWithSuccess- cause: " + cause + " smsSource: " + smsSource + + " token: " + token + " messageRef: " + messageRef); + if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) { + acknowledgeImsSms(token, messageRef, true, null); + } else { + mCi.acknowledgeLastIncomingGsmSms(true, cause, null); + } } - private void acknowledgeSmsWithError(int cause) { - mCi.acknowledgeLastIncomingGsmSms(false, cause, null); + private void acknowledgeSmsWithError(int cause, int smsSource, int token, int messageRef) { + Rlog.d(TAG, "acknowledgeSmsWithError- cause: " + cause + " smsSource: " + smsSource + + " token: " + token + " messageRef: " + messageRef); + if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) { + acknowledgeImsSms(token, messageRef, false, null); + } else { + mCi.acknowledgeLastIncomingGsmSms(false, cause, null); + } } /** @@ -299,6 +336,45 @@ public class UsimDataDownloadHandler extends Handler { PhoneFactory.getPhone(mPhoneId).getSmsStats().onIncomingSmsPP(smsSource, result); } + /** + * Route resposes via ImsManager based on config + */ + private boolean ackViaIms() { + boolean isViaIms; + + try { + isViaIms = mResource.getBoolean( + com.android.internal.R.bool.config_smppsim_response_via_ims); + } catch (NotFoundException e) { + isViaIms = false; + } + + Rlog.d(TAG, "ackViaIms : " + isViaIms); + return isViaIms; + } + + /** + * Acknowledges IMS SMS and delivers the result based on the envelope or SIM saving respose + * received from SIM for SMS-PP Data. + */ + private void acknowledgeImsSms(int token, int messageRef, boolean success, byte[] pdu) { + int result = success ? ImsSmsImplBase.DELIVER_STATUS_OK : + ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC; + Rlog.d(TAG, "sending result via acknowledgeImsSms: " + result + " token: " + token); + + try { + if (mImsManager != null) { + if (pdu != null && pdu.length > 0) { + mImsManager.acknowledgeSms(token, messageRef, result, pdu); + } else { + mImsManager.acknowledgeSms(token, messageRef, result); + } + } + } catch (ImsException e) { + Rlog.e(TAG, "Failed to acknowledgeSms(). Error: " + e.getMessage()); + } + } + /** * Handle UICC envelope response and send SMS acknowledgement. * @@ -307,35 +383,60 @@ public class UsimDataDownloadHandler extends Handler { @Override public void handleMessage(Message msg) { AsyncResult ar; + int smsSource = InboundSmsHandler.SOURCE_INJECTED_FROM_UNKNOWN; + int token = 0; + int messageRef = 0; + int[] responseInfo; switch (msg.what) { case EVENT_START_DATA_DOWNLOAD: - handleDataDownload((SmsMessage) msg.obj, msg.arg1 /* smsSource */); + Rlog.d(TAG, "EVENT_START_DATA_DOWNLOAD"); + handleDataDownload((SmsMessage) msg.obj, msg.arg1 /* smsSource */, + msg.arg2 /* token */); break; case EVENT_SEND_ENVELOPE_RESPONSE: ar = (AsyncResult) msg.obj; + responseInfo = (int[]) ar.userObj; + smsSource = responseInfo[2]; + messageRef = responseInfo[3]; + token = responseInfo[4]; + + Rlog.d(TAG, "Received EVENT_SEND_ENVELOPE_RESPONSE from source : " + smsSource); + if (ar.exception != null) { Rlog.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception); + acknowledgeSmsWithError( - CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR); + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, + smsSource, token, messageRef); return; } - int[] dcsPid = (int[]) ar.userObj; - sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, dcsPid[0], dcsPid[1]); + Rlog.d(TAG, "Successful in sending envelope response"); + sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, responseInfo[0], + responseInfo[1], smsSource, token, messageRef); break; case EVENT_WRITE_SMS_COMPLETE: ar = (AsyncResult) msg.obj; + + responseInfo = (int[]) ar.userObj; + smsSource = responseInfo[0]; + messageRef = responseInfo[1]; + token = responseInfo[2]; + + Rlog.d(TAG, "Received EVENT_WRITE_SMS_COMPLETE from source : " + smsSource); + if (ar.exception == null) { Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC"); - mCi.acknowledgeLastIncomingGsmSms(true, 0, null); + acknowledgeSmsWithSuccess(0, smsSource, token, messageRef); } else { Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception); - mCi.acknowledgeLastIncomingGsmSms(false, - CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null); + acknowledgeSmsWithError( + CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, + smsSource, token, messageRef); } break; @@ -343,4 +444,23 @@ public class UsimDataDownloadHandler extends Handler { Rlog.e(TAG, "Ignoring unexpected message, what=" + msg.what); } } + + /** + * Called when ImsManager connection is ready. ImsManager object will be used to send ACK to IMS + * which doesn't use RIL interface. + * @param imsManager object + */ + public void setImsManager(ImsManager imsManager) { + mImsManager = imsManager; + } + + /** + * Called to set mocked object of type Resources during unit testing of this file. + * @param resource object + */ + @VisibleForTesting + public void setResourcesForTest(Resources resource) { + mResource = resource; + Rlog.d(TAG, "setResourcesForTest"); + } } diff --git a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java old mode 100755 new mode 100644 index e594ab6c1fc4fa71d673582b1834089d214582c5..48be16c3e783290abe52713ff89aea45fdb066bd --- a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java +++ b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java @@ -32,6 +32,7 @@ import com.android.internal.telephony.uicc.IccUtils; import com.android.telephony.Rlog; import java.util.ArrayList; +import java.util.Locale; /** * This class implements reading and parsing USIM records. @@ -233,7 +234,7 @@ public class UsimPhoneBookManager extends Handler implements IccConstants { int emailEfid = email.getEfid(); log("EF_EMAIL exists in PBR. efid = 0x" + - Integer.toHexString(emailEfid).toUpperCase()); + Integer.toHexString(emailEfid).toUpperCase(Locale.ROOT)); /** * Make sure this EF_EMAIL was never read earlier. Sometimes two PBR record points @@ -348,7 +349,7 @@ public class UsimPhoneBookManager extends Handler implements IccConstants { emailList = new ArrayList(); } log("Adding email #" + i + " list to index 0x" + - Integer.toHexString(index).toUpperCase()); + Integer.toHexString(index).toUpperCase(Locale.ROOT)); emailList.add(email); mEmailsForAdnRec.put(index, emailList); } @@ -402,7 +403,7 @@ public class UsimPhoneBookManager extends Handler implements IccConstants { } emailList.add(email); log("Adding email list to index 0x" + - Integer.toHexString(index).toUpperCase()); + Integer.toHexString(index).toUpperCase(Locale.ROOT)); mEmailsForAdnRec.put(index, emailList); } } @@ -446,8 +447,9 @@ public class UsimPhoneBookManager extends Handler implements IccConstants { System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size()); rec.setEmails(emails); log("Adding email list to ADN (0x" + - Integer.toHexString(mPhoneBookRecords.get(i).getEfid()).toUpperCase() + - ") record #" + mPhoneBookRecords.get(i).getRecId()); + Integer.toHexString(mPhoneBookRecords.get(i).getEfid()) + .toUpperCase(Locale.ROOT) + ") record #" + + mPhoneBookRecords.get(i).getRecId()); mPhoneBookRecords.set(i, rec); } } diff --git a/src/java/com/android/internal/telephony/ims/ImsEnablementTracker.java b/src/java/com/android/internal/telephony/ims/ImsEnablementTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..e54561f15d59e3cbb023ed82bbe2cf99265c19fa --- /dev/null +++ b/src/java/com/android/internal/telephony/ims/ImsEnablementTracker.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.ims; + +import android.content.ComponentName; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.SubscriptionManager; +import android.telephony.ims.aidl.IImsServiceController; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IState; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class will abstract away all the new enablement logic and take the reset/enable/disable + * IMS commands as inputs. + * The IMS commands will call enableIms, disableIms or resetIms to match the enablement state only + * when it changes. + */ +public class ImsEnablementTracker { + private static final String LOG_TAG = "ImsEnablementTracker"; + private static final long REQUEST_THROTTLE_TIME_MS = 3 * 1000L; // 3 seconds + + private static final int COMMAND_NONE_MSG = 0; + // Indicate that the enableIms command has been received. + @VisibleForTesting + public static final int COMMAND_ENABLE_MSG = 1; + // Indicate that the disableIms command has been received. + @VisibleForTesting + public static final int COMMAND_DISABLE_MSG = 2; + // Indicate that the resetIms command has been received. + private static final int COMMAND_RESET_MSG = 3; + // Indicate that the internal enable message with delay has been received. + private static final int COMMAND_ENABLING_DONE = 4; + // Indicate that the internal disable message with delay has been received. + private static final int COMMAND_DISABLING_DONE = 5; + // Indicate that the internal reset message with delay has been received. + @VisibleForTesting + public static final int COMMAND_RESETTING_DONE = 6; + // The ImsServiceController binder is connected. + private static final int COMMAND_CONNECTED_MSG = 7; + // The ImsServiceController binder is disconnected. + private static final int COMMAND_DISCONNECTED_MSG = 8; + // The subId is changed to INVALID_SUBSCRIPTION_ID. + private static final int COMMAND_INVALID_SUBID_MSG = 9; + // Indicate that the internal post reset message with delay has been received. + @VisibleForTesting + public static final int COMMAND_POST_RESETTING_DONE = 10; + + private static final Map EVENT_DESCRIPTION = new HashMap<>(); + static { + EVENT_DESCRIPTION.put(COMMAND_NONE_MSG, "COMMAND_NONE_MSG"); + EVENT_DESCRIPTION.put(COMMAND_ENABLE_MSG, "COMMAND_ENABLE_MSG"); + EVENT_DESCRIPTION.put(COMMAND_DISABLE_MSG, "COMMAND_DISABLE_MSG"); + EVENT_DESCRIPTION.put(COMMAND_RESET_MSG, "COMMAND_RESET_MSG"); + EVENT_DESCRIPTION.put(COMMAND_ENABLING_DONE, "COMMAND_ENABLING_DONE"); + EVENT_DESCRIPTION.put(COMMAND_DISABLING_DONE, "COMMAND_DISABLING_DONE"); + EVENT_DESCRIPTION.put(COMMAND_RESETTING_DONE, "COMMAND_RESETTING_DONE"); + EVENT_DESCRIPTION.put(COMMAND_CONNECTED_MSG, "COMMAND_CONNECTED_MSG"); + EVENT_DESCRIPTION.put(COMMAND_DISCONNECTED_MSG, "COMMAND_DISCONNECTED_MSG"); + EVENT_DESCRIPTION.put(COMMAND_INVALID_SUBID_MSG, "COMMAND_INVALID_SUBID_MSG"); + } + + @VisibleForTesting + protected static final int STATE_IMS_DISCONNECTED = 0; + @VisibleForTesting + protected static final int STATE_IMS_DEFAULT = 1; + @VisibleForTesting + protected static final int STATE_IMS_ENABLED = 2; + @VisibleForTesting + protected static final int STATE_IMS_DISABLING = 3; + @VisibleForTesting + protected static final int STATE_IMS_DISABLED = 4; + @VisibleForTesting + protected static final int STATE_IMS_ENABLING = 5; + @VisibleForTesting + protected static final int STATE_IMS_RESETTING = 6; + + @VisibleForTesting + protected static final int STATE_IMS_POSTRESETTING = 7; + + protected final Object mLock = new Object(); + private IImsServiceController mIImsServiceController; + private long mLastImsOperationTimeMs = 0L; + private final ComponentName mComponentName; + private final SparseArray mStateMachines; + + private final Looper mLooper; + private final int mState; + + /** + * Provides Ims Enablement Tracker State Machine responsible for ims enable/disable/reset + * command interactions with Ims service controller binder. + * The enable/disable/reset ims commands have a time interval of at least + * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second between + * processing each command. + * For example, the enableIms command is received and the binder's enableIms is called. + * After that, if the disableIms command is received, the binder's disableIms will be + * called after {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second. + * A time of {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} will be used + * {@link Handler#sendMessageDelayed(Message, long)}, + * and the enabled, disabled and reset states are responsible for waiting for + * that delay message. + */ + class ImsEnablementTrackerStateMachine extends StateMachine { + /** + * The initial state of this class and waiting for an ims commands. + */ + @VisibleForTesting + public final Default mDefault; + + /** + * Indicates that {@link IImsServiceController#enableIms(int, int)} has been called and + * waiting for an ims commands. + * Common transitions are to + * {@link #mDisabling} state when the disable command is received + * or {@link #mResetting} state when the reset command is received + * or {@link #mDisconnected} if the binder is disconnected. + */ + @VisibleForTesting + public final Enabled mEnabled; + + /** + * Indicates that the state waiting for the throttle time to elapse before calling + * {@link IImsServiceController#disableIms(int, int)}. + * Common transitions are to + * {@link #mEnabled} when the enable command is received. + * or {@link #mResetting} when the reset command is received. + * or {@link #mDisabled} the previous binder API call has passed + * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second, and if + * {@link IImsServiceController#disableIms(int, int)} called. + * or {@link #mDisabling} received a disableIms message and the previous binder API call + * has not passed {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second. + * Then send a disableIms message with delay. + * or {@link #mDisconnected} if the binder is disconnected. + */ + @VisibleForTesting + public final Disabling mDisabling; + + /** + * Indicates that {@link IImsServiceController#disableIms(int, int)} has been called and + * waiting for an ims commands. + * Common transitions are to + * {@link #mEnabling} state when the enable command is received. + * or {@link #mDisconnected} if the binder is disconnected. + */ + @VisibleForTesting + public final Disabled mDisabled; + + /** + * Indicates that the state waiting for the throttle time to elapse before calling + * {@link IImsServiceController#enableIms(int, int)}. + * Common transitions are to + * {@link #mEnabled} the previous binder API call has passed + * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second, and + * {@link IImsServiceController#enableIms(int, int)} called. + * or {@link #mDisabled} when the disable command is received. + * or {@link #mEnabling} received an enableIms message and the previous binder API call + * has not passed {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second. + * Then send an enableIms message with delay. + * or {@link #mDisconnected} if the binder is disconnected. + */ + @VisibleForTesting + public final Enabling mEnabling; + + /** + * Indicates that the state waiting for the throttle time to elapse before calling + * {@link IImsServiceController#resetIms(int, int)}. + * Common transitions are to + * {@link #mPostResetting} state to call either enableIms or disableIms after calling + * {@link IImsServiceController#resetIms(int, int)} + * or {@link #mDisconnected} if the binder is disconnected. + */ + @VisibleForTesting + public final Resetting mResetting; + + /** + * Indicates that the state waiting after resetIms for the throttle time to elapse before + * calling {@link IImsServiceController#enableIms(int, int)} or + * {@link IImsServiceController#disableIms(int, int)}. + * Common transitions are to + * {@link #mEnabled} state when the disable command is received, + * {@link #mDisabled} state when the enable command is received after calling + * {@link IImsServiceController#enableIms(int, int)}, + * {@link IImsServiceController#disableIms(int, int)} + * or {@link #mDisconnected} if the binder is disconnected. + */ + public final PostResetting mPostResetting; + + /** + * Indicates that {@link IImsServiceController} has not been set. + * Common transition is to + * {@link #mDefault} state when the binder is set. + * or {@link #mDisabling} If the disable command is received while the binder is + * disconnected + * or {@link #mEnabling} If the enable command is received while the binder is + * disconnected + */ + + private final Disconnected mDisconnected; + private int mSlotId; + private int mSubId; + + private final int mPhoneId; + + private IState mPreviousState; + + private int mLastMsg = COMMAND_NONE_MSG; + + ImsEnablementTrackerStateMachine(String name, Looper looper, int state, int slotId) { + super(name, looper); + mPhoneId = slotId; + mDefault = new Default(); + mEnabled = new Enabled(); + mDisabling = new Disabling(); + mDisabled = new Disabled(); + mEnabling = new Enabling(); + mResetting = new Resetting(); + mDisconnected = new Disconnected(); + mPostResetting = new PostResetting(); + + addState(mDefault); + addState(mEnabled); + addState(mDisabling); + addState(mDisabled); + addState(mEnabling); + addState(mResetting); + addState(mDisconnected); + addState(mPostResetting); + + setInitialState(getState(state)); + mPreviousState = getState(state); + } + + public void clearAllMessage() { + Log.d(LOG_TAG, "clearAllMessage"); + removeMessages(COMMAND_ENABLE_MSG); + removeMessages(COMMAND_DISABLE_MSG); + removeMessages(COMMAND_RESET_MSG); + removeMessages(COMMAND_ENABLING_DONE); + removeMessages(COMMAND_DISABLING_DONE); + removeMessages(COMMAND_RESETTING_DONE); + removeMessages(COMMAND_POST_RESETTING_DONE); + } + + public void serviceBinderConnected() { + clearAllMessage(); + sendMessage(COMMAND_CONNECTED_MSG); + } + + public void serviceBinderDisconnected() { + clearAllMessage(); + sendMessage(COMMAND_DISCONNECTED_MSG); + } + + @VisibleForTesting + public boolean isState(int state) { + State expect = null; + switch (state) { + case Default.STATE_NO: + expect = mDefault; + break; + case Enabled.STATE_NO: + expect = mEnabled; + break; + case Disabling.STATE_NO: + expect = mDisabling; + break; + case Disabled.STATE_NO: + expect = mDisabled; + break; + case Enabling.STATE_NO: + expect = mEnabling; + break; + case Resetting.STATE_NO: + expect = mResetting; + break; + case Disconnected.STATE_NO: + expect = mDisconnected; + break; + case PostResetting.STATE_NO: + expect = mPostResetting; + break; + default: + break; + } + return (getCurrentState() == expect) ? true : false; + } + + private State getState(int state) { + switch (state) { + case ImsEnablementTracker.STATE_IMS_ENABLED: + return mEnabled; + case ImsEnablementTracker.STATE_IMS_DISABLING: + return mDisabling; + case ImsEnablementTracker.STATE_IMS_DISABLED: + return mDisabled; + case ImsEnablementTracker.STATE_IMS_ENABLING: + return mEnabling; + case ImsEnablementTracker.STATE_IMS_RESETTING: + return mResetting; + case ImsEnablementTracker.STATE_IMS_DISCONNECTED: + return mDisconnected; + case ImsEnablementTracker.STATE_IMS_POSTRESETTING: + return mPostResetting; + default: + return mDefault; + } + } + + private void handleInvalidSubIdMessage() { + clearAllMessage(); + transitionState(mDefault); + } + + private void transitionState(State state) { + mPreviousState = getCurrentState(); + transitionTo(state); + } + + class Default extends State { + private static final int STATE_NO = STATE_IMS_DEFAULT; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]Default state:enter"); + mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]Default state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + // When enableIms() is called, enableIms of binder is call and the state + // change to the enabled state. + case COMMAND_ENABLE_MSG: + sendEnableIms(message.arg1, message.arg2); + transitionState(mEnabled); + return HANDLED; + // When disableIms() is called, disableIms of binder is call and the state + // change to the disabled state. + case COMMAND_DISABLE_MSG: + sendDisableIms(message.arg1, message.arg2); + transitionState(mDisabled); + return HANDLED; + // When resetIms() is called, change to the resetting state to call enableIms + // after calling resetIms of binder. + case COMMAND_RESET_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + transitionState(mResetting); + return HANDLED; + case COMMAND_DISCONNECTED_MSG: + transitionState(mDisconnected); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class Enabled extends State { + private static final int STATE_NO = STATE_IMS_ENABLED; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]Enabled state:enter"); + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]Enabled state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + // the disableIms() is called. + case COMMAND_DISABLE_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + transitionState(mDisabling); + return HANDLED; + // the resetIms() is called. + case COMMAND_RESET_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + transitionState(mResetting); + return HANDLED; + case COMMAND_DISCONNECTED_MSG: + transitionState(mDisconnected); + return HANDLED; + case COMMAND_INVALID_SUBID_MSG: + handleInvalidSubIdMessage(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class Disabling extends State { + private static final int STATE_NO = STATE_IMS_DISABLING; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]Disabling state:enter"); + sendMessageDelayed(COMMAND_DISABLING_DONE, mSlotId, mSubId, + getRemainThrottleTime()); + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]Disabling state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + case COMMAND_ENABLE_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + clearAllMessage(); + if (mPreviousState == mResetting) { + // if we are moving from Resetting -> Disabling and receive + // the COMMAND_ENABLE_MSG, we need to send the enableIms command, + // so move to Enabling state. + transitionState(mEnabling); + } else { + // When moving from Enabled -> Disabling and we receive an ENABLE_MSG, + // we can move straight back to Enabled state because we have not sent + // the disableIms command to IMS yet. + transitionState(mEnabled); + } + return HANDLED; + case COMMAND_DISABLING_DONE: + // If the disable command is received before disableIms is processed, + // it will be ignored because the disable command processing is in progress. + removeMessages(COMMAND_DISABLE_MSG); + sendDisableIms(message.arg1, message.arg2); + transitionState(mDisabled); + return HANDLED; + case COMMAND_RESET_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + clearAllMessage(); + transitionState(mResetting); + return HANDLED; + case COMMAND_DISCONNECTED_MSG: + transitionState(mDisconnected); + return HANDLED; + case COMMAND_INVALID_SUBID_MSG: + handleInvalidSubIdMessage(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class Disabled extends State { + private static final int STATE_NO = STATE_IMS_DISABLED; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]Disabled state:enter"); + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]Disabled state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + case COMMAND_ENABLE_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + transitionState(mEnabling); + return HANDLED; + case COMMAND_RESET_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + transitionState(mResetting); + return HANDLED; + case COMMAND_DISCONNECTED_MSG: + transitionState(mDisconnected); + return HANDLED; + case COMMAND_INVALID_SUBID_MSG: + handleInvalidSubIdMessage(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class Enabling extends State { + private static final int STATE_NO = STATE_IMS_ENABLING; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]Enabling state:enter"); + sendMessageDelayed(COMMAND_ENABLING_DONE, mSlotId, mSubId, getRemainThrottleTime()); + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]Enabling state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + // Enabling state comes from Resetting and disableIms() is called. + // In this case disableIms() of binder should be called. + // When enabling state comes from disabled, just change state to the disabled. + case COMMAND_DISABLE_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + clearAllMessage(); + if (mPreviousState == mResetting) { + transitionState(mDisabling); + } else { + transitionState(mDisabled); + } + return HANDLED; + case COMMAND_RESET_MSG: + mSlotId = message.arg1; + mSubId = message.arg2; + transitionState(mResetting); + return HANDLED; + case COMMAND_ENABLING_DONE: + // If the enable command is received before enableIms is processed, + // it will be ignored because the enable command processing is in progress. + removeMessages(COMMAND_ENABLE_MSG); + sendEnableIms(message.arg1, message.arg2); + transitionState(mEnabled); + return HANDLED; + case COMMAND_DISCONNECTED_MSG: + transitionState(mDisconnected); + return HANDLED; + case COMMAND_INVALID_SUBID_MSG: + handleInvalidSubIdMessage(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class Resetting extends State { + private static final int STATE_NO = STATE_IMS_RESETTING; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]Resetting state:enter"); + sendMessageDelayed(COMMAND_RESETTING_DONE, mSlotId, mSubId, + getRemainThrottleTime()); + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]Resetting state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + case COMMAND_DISABLE_MSG: + mLastMsg = COMMAND_DISABLE_MSG; + return HANDLED; + case COMMAND_ENABLE_MSG: + mLastMsg = COMMAND_ENABLE_MSG; + return HANDLED; + case COMMAND_RESETTING_DONE: + mSlotId = message.arg1; + mSubId = message.arg2; + // If the reset command is received before disableIms is processed, + // it will be ignored because the reset command processing is in progress. + removeMessages(COMMAND_RESET_MSG); + sendResetIms(mSlotId, mSubId); + transitionState(mPostResetting); + return HANDLED; + case COMMAND_DISCONNECTED_MSG: + transitionState(mDisconnected); + return HANDLED; + case COMMAND_INVALID_SUBID_MSG: + handleInvalidSubIdMessage(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class Disconnected extends State { + private static final int STATE_NO = STATE_IMS_DISCONNECTED; + + private int mLastMsg = COMMAND_NONE_MSG; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]Disconnected state:enter"); + clearAllMessage(); + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]Disconnected state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + case COMMAND_CONNECTED_MSG: + clearAllMessage(); + transitionState(mDefault); + if (mLastMsg != COMMAND_NONE_MSG) { + sendMessageDelayed(mLastMsg, mSlotId, mSubId, 0); + mLastMsg = COMMAND_NONE_MSG; + } + return HANDLED; + case COMMAND_ENABLE_MSG: + case COMMAND_DISABLE_MSG: + case COMMAND_RESET_MSG: + mLastMsg = message.what; + mSlotId = message.arg1; + mSubId = message.arg2; + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class PostResetting extends State { + private static final int STATE_NO = STATE_IMS_POSTRESETTING; + + @Override + public void enter() { + Log.d(LOG_TAG, "[" + mPhoneId + "]PostResetting state:enter"); + sendMessageDelayed(COMMAND_POST_RESETTING_DONE, mSlotId, mSubId, + getRemainThrottleTime()); + } + + @Override + public void exit() { + mLastMsg = COMMAND_NONE_MSG; + } + + @Override + public boolean processMessage(Message message) { + Log.d(LOG_TAG, "[" + mPhoneId + "]PostResetting state:processMessage. msg.what=" + + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName); + + switch (message.what) { + case COMMAND_POST_RESETTING_DONE: + mSlotId = message.arg1; + mSubId = message.arg2; + if (mLastMsg == COMMAND_DISABLE_MSG) { + sendDisableIms(mSlotId, mSubId); + transitionState(mDisabled); + } else { + // if mLastMsg is COMMAND_NONE_MSG or COMMAND_ENABLE_MSG + sendEnableIms(mSlotId, mSubId); + transitionState(mEnabled); + } + return HANDLED; + case COMMAND_ENABLE_MSG: + case COMMAND_DISABLE_MSG: + mLastMsg = message.what; + mSlotId = message.arg1; + mSubId = message.arg2; + return HANDLED; + case COMMAND_RESET_MSG: + // when resetIms() called again, skip to call + // IImsServiceController.resetIms(slotId, subId), but after throttle time + // IImsServiceController.enableIms(slotId, subId) should be called. + mLastMsg = COMMAND_ENABLE_MSG; + mSlotId = message.arg1; + mSubId = message.arg2; + return HANDLED; + case COMMAND_DISCONNECTED_MSG: + transitionState(mDisconnected); + return HANDLED; + case COMMAND_INVALID_SUBID_MSG: + handleInvalidSubIdMessage(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + } + + public ImsEnablementTracker(Looper looper, ComponentName componentName) { + mIImsServiceController = null; + mStateMachines = new SparseArray<>(); + mLooper = looper; + mState = ImsEnablementTracker.STATE_IMS_DISCONNECTED; + mComponentName = componentName; + } + + @VisibleForTesting + public ImsEnablementTracker(Looper looper, IImsServiceController controller, int state, + int numSlots) { + mIImsServiceController = controller; + mStateMachines = new SparseArray<>(); + mLooper = looper; + mState = state; + mComponentName = null; + + setNumOfSlots(numSlots); + } + + /** + * Set the number of SIM slots. + * @param numOfSlots the number of SIM slots. + */ + public void setNumOfSlots(int numOfSlots) { + int oldNumSlots = mStateMachines.size(); + Log.d(LOG_TAG, "set the slots: old[" + oldNumSlots + "], new[" + numOfSlots + "]," + + "component:" + mComponentName); + if (numOfSlots == oldNumSlots) { + return; + } + ImsEnablementTrackerStateMachine enablementStateMachine = null; + if (oldNumSlots < numOfSlots) { + for (int i = oldNumSlots; i < numOfSlots; i++) { + enablementStateMachine = new ImsEnablementTrackerStateMachine( + "ImsEnablementTracker", mLooper, mState, i); + enablementStateMachine.start(); + mStateMachines.put(i, enablementStateMachine); + } + } else if (oldNumSlots > numOfSlots) { + for (int i = (oldNumSlots - 1); i > (numOfSlots - 1); i--) { + enablementStateMachine = mStateMachines.get(i); + mStateMachines.remove(i); + enablementStateMachine.quitNow(); + } + } + } + + @VisibleForTesting + public Handler getHandler(int slotId) { + return mStateMachines.get(slotId).getHandler(); + } + + /** + * Check that the current state and the input state are the same. + * @param state the input state. + * @return true if the current state and input state are the same or false. + */ + @VisibleForTesting + public boolean isState(int slotId, int state) { + return mStateMachines.get(slotId).isState(state); + } + + /** + * Notify the state machine that the subId has changed to invalid. + * @param slotId subscription id + */ + public void subIdChangedToInvalid(int slotId) { + Log.d(LOG_TAG, "[" + slotId + "] subId changed to invalid, component:" + mComponentName); + ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId); + if (stateMachine != null) { + stateMachine.sendMessage(COMMAND_INVALID_SUBID_MSG, slotId); + } else { + Log.w(LOG_TAG, "There is no state machine associated with this slotId."); + } + } + + /** + * Notify ImsService to enable IMS for the framework. This will trigger IMS registration and + * trigger ImsFeature status updates. + * @param slotId slot id + * @param subId subscription id + */ + public void enableIms(int slotId, int subId) { + Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]enableIms, component:" + mComponentName); + ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId); + if (stateMachine != null) { + stateMachine.sendMessage(COMMAND_ENABLE_MSG, slotId, subId); + } else { + Log.w(LOG_TAG, "There is no state machine associated with this slotId."); + } + } + + /** + * Notify ImsService to disable IMS for the framework. This will trigger IMS de-registration and + * trigger ImsFeature capability status to become false. + * @param slotId slot id + * @param subId subscription id + */ + public void disableIms(int slotId, int subId) { + Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]disableIms, component:" + mComponentName); + ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId); + if (stateMachine != null) { + stateMachine.sendMessage(COMMAND_DISABLE_MSG, slotId, subId); + } else { + Log.w(LOG_TAG, "There is no state machine associated with this slotId."); + } + } + + /** + * Notify ImsService to reset IMS for the framework. This will trigger ImsService to perform + * de-registration and release all resource. After that, if enaleIms is called, the ImsService + * performs registration and appropriate initialization to bring up all ImsFeatures. + * @param slotId slot id + * @param subId subscription id + */ + public void resetIms(int slotId, int subId) { + Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]resetIms, component:" + mComponentName); + ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId); + if (stateMachine != null) { + stateMachine.sendMessage(COMMAND_RESET_MSG, slotId, subId); + } else { + Log.w(LOG_TAG, "There is no state machine associated with this slotId."); + } + } + + /** + * Sets the IImsServiceController instance. + */ + protected void setServiceController(IBinder serviceController) { + synchronized (mLock) { + mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController); + Log.d(LOG_TAG, "setServiceController with Binder:" + mIImsServiceController + + ", component:" + mComponentName); + ImsEnablementTrackerStateMachine stateMachine = null; + for (int i = 0; i < mStateMachines.size(); i++) { + stateMachine = mStateMachines.get(i); + if (stateMachine == null) { + Log.w(LOG_TAG, "There is no state machine associated with" + + "the slotId[" + i + "]"); + continue; + } + if (isServiceControllerAvailable()) { + stateMachine.serviceBinderConnected(); + } else { + stateMachine.serviceBinderDisconnected(); + } + } + } + } + + protected long getLastOperationTimeMillis() { + return mLastImsOperationTimeMs; + } + + /** + * Get remaining throttle time value + * @return remaining throttle time value + */ + @VisibleForTesting + public long getRemainThrottleTime() { + long remainTime = REQUEST_THROTTLE_TIME_MS - (System.currentTimeMillis() + - getLastOperationTimeMillis()); + + if (remainTime < 0) { + remainTime = 0L; + } + Log.d(LOG_TAG, "getRemainThrottleTime:" + remainTime); + + return remainTime; + } + + /** + * Check to see if the service controller is available. + * @return true if available, false otherwise + */ + private boolean isServiceControllerAvailable() { + if (mIImsServiceController != null) { + return true; + } + Log.d(LOG_TAG, "isServiceControllerAvailable : binder is not alive"); + return false; + } + + private void sendEnableIms(int slotId, int subId) { + try { + synchronized (mLock) { + if (isServiceControllerAvailable()) { + Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendEnableIms," + + "componentName[" + mComponentName + "]"); + mIImsServiceController.enableIms(slotId, subId); + mLastImsOperationTimeMs = System.currentTimeMillis(); + } + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage()); + } + } + + private void sendDisableIms(int slotId, int subId) { + try { + synchronized (mLock) { + if (isServiceControllerAvailable()) { + Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendDisableIms" + + " componentName[" + mComponentName + "]"); + mIImsServiceController.disableIms(slotId, subId); + mLastImsOperationTimeMs = System.currentTimeMillis(); + } + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage()); + } + } + + private void sendResetIms(int slotId, int subId) { + try { + synchronized (mLock) { + if (isServiceControllerAvailable()) { + Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendResetIms"); + mIImsServiceController.resetIms(slotId, subId); + mLastImsOperationTimeMs = System.currentTimeMillis(); + } + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Couldn't reset IMS: " + e.getMessage()); + } + } +} diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java index 0cb58aaa11d0fc24d204ebbcce0a6b1cc83338d5..49b7e628e13ef750e19728d01b0d8cf5ae0f5454 100644 --- a/src/java/com/android/internal/telephony/ims/ImsResolver.java +++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java @@ -30,6 +30,7 @@ import android.content.pm.ServiceInfo; import android.os.AsyncResult; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; @@ -130,6 +131,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal // Delay between dynamic ImsService queries. private static final int DELAY_DYNAMIC_QUERY_MS = 5000; + private static final HandlerThread sHandlerThread = new HandlerThread(TAG); private static ImsResolver sInstance; @@ -139,9 +141,9 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal public static void make(Context context, String defaultMmTelPackageName, String defaultRcsPackageName, int numSlots, ImsFeatureBinderRepository repo) { if (sInstance == null) { - Looper looper = Looper.getMainLooper(); + sHandlerThread.start(); sInstance = new ImsResolver(context, defaultMmTelPackageName, defaultRcsPackageName, - numSlots, repo, looper); + numSlots, repo, sHandlerThread.getLooper()); } } @@ -630,8 +632,13 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal /** * Needs to be called after the constructor to kick off the process of binding to ImsServices. + * Should be run on the handler thread of ImsResolver */ public void initialize() { + mHandler.post(()-> initializeInternal()); + } + + private void initializeInternal() { mEventLog.log("Initializing"); Log.i(TAG, "Initializing cache."); PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler, @@ -737,6 +744,15 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal (controller) -> controller.disableIms(slotId, getSubId(slotId))); } + /** + * Notify ImsService to disable IMS for the framework. + * And notify ImsService back to enable IMS for the framework. + */ + public void resetIms(int slotId) { + getImsServiceControllers(slotId).forEach( + (controller) -> controller.resetIms(slotId, getSubId(slotId))); + } + /** * Returns the ImsRegistration structure associated with the slotId and feature specified. */ diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java index 92e7d7117cba3aff5684f0c1d83fa39d72c7d830..6af7a08a4c4432fb88c8f23004fb0f94cc70bcbc 100644 --- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java +++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java @@ -85,14 +85,49 @@ public class ImsServiceController { @Override public void onServiceConnected(ComponentName name, IBinder service) { + if (mHandler.getLooper().isCurrentThread()) { + onServiceConnectedInternal(name, service); + } else { + mHandler.post(() -> onServiceConnectedInternal(name, service)); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (mHandler.getLooper().isCurrentThread()) { + onServiceDisconnectedInternal(name); + } else { + mHandler.post(() -> onServiceDisconnectedInternal(name)); + } + } + + @Override + public void onBindingDied(ComponentName name) { + if (mHandler.getLooper().isCurrentThread()) { + onBindingDiedInternal(name); + } else { + mHandler.post(() -> onBindingDiedInternal(name)); + } + } + + @Override + public void onNullBinding(ComponentName name) { + if (mHandler.getLooper().isCurrentThread()) { + onNullBindingInternal(name); + } else { + mHandler.post(() -> onNullBindingInternal(name)); + } + } + + private void onServiceConnectedInternal(ComponentName name, IBinder service) { synchronized (mLock) { mBackoff.stop(); mIsBound = true; mIsBinding = false; try { - mLocalLog.log("onServiceConnected"); - Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: " - + service); + mLocalLog.log("onServiceConnectedInternal"); + Log.d(LOG_TAG, "ImsService(" + name + + "): onServiceConnectedInternal with binder: " + service); setServiceController(service); notifyImsServiceReady(); retrieveStaticImsServiceCapabilities(); @@ -118,20 +153,18 @@ public class ImsServiceController { } } - @Override - public void onServiceDisconnected(ComponentName name) { + private void onServiceDisconnectedInternal(ComponentName name) { synchronized (mLock) { mIsBinding = false; cleanupConnection(); } - mLocalLog.log("onServiceDisconnected"); - Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting..."); + mLocalLog.log("onServiceDisconnectedInternal"); + Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnectedInternal. Waiting..."); // Service disconnected, but we are still technically bound. Waiting for reconnect. checkAndReportAnomaly(name); } - @Override - public void onBindingDied(ComponentName name) { + private void onBindingDiedInternal(ComponentName name) { mIsServiceConnectionDead = true; synchronized (mLock) { mIsBinding = false; @@ -141,15 +174,15 @@ public class ImsServiceController { unbindService(); startDelayedRebindToService(); } - Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind..."); - mLocalLog.log("onBindingDied, retrying in " + mBackoff.getCurrentDelay() + " mS"); + Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDiedInternal. Starting rebind..."); + mLocalLog.log("onBindingDiedInternal, retrying in " + + mBackoff.getCurrentDelay() + " mS"); } - @Override - public void onNullBinding(ComponentName name) { - Log.w(LOG_TAG, "ImsService(" + name + "): onNullBinding. Is service dead = " + private void onNullBindingInternal(ComponentName name) { + Log.w(LOG_TAG, "ImsService(" + name + "): onNullBindingInternal. Is service dead = " + mIsServiceConnectionDead); - mLocalLog.log("onNullBinding, is service dead = " + mIsServiceConnectionDead); + mLocalLog.log("onNullBindingInternal, is service dead = " + mIsServiceConnectionDead); // onNullBinding will happen after onBindingDied. In this case, we should not // permanently unbind and instead let the automatic rebind occur. if (mIsServiceConnectionDead) return; @@ -230,6 +263,7 @@ public class ImsServiceController { private static final boolean ENFORCE_SINGLE_SERVICE_FOR_SIP_TRANSPORT = false; private final ComponentName mComponentName; private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler"); + private final Handler mHandler; private final LegacyPermissionManager mPermissionManager; private ImsFeatureBinderRepository mRepo; private ImsServiceControllerCallbacks mCallbacks; @@ -241,6 +275,7 @@ public class ImsServiceController { private Set mImsFeatures; private SparseIntArray mSlotIdToSubIdMap; private IImsServiceController mIImsServiceController; + private final ImsEnablementTracker mImsEnablementTracker; // The Capabilities bitmask of the connected ImsService (see ImsService#ImsServiceCapability). private long mServiceCapabilities; private ImsServiceConnection mImsServiceConnection; @@ -323,16 +358,17 @@ public class ImsServiceController { mComponentName = componentName; mCallbacks = callbacks; mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); mBackoff = new ExponentialBackoff( mRebindRetry.getStartDelay(), mRebindRetry.getMaximumDelay(), 2, /* multiplier */ - mHandlerThread.getLooper(), + mHandler, mRestartImsServiceRunnable); mPermissionManager = (LegacyPermissionManager) mContext.getSystemService( Context.LEGACY_PERMISSION_SERVICE); mRepo = repo; - + mImsEnablementTracker = new ImsEnablementTracker(mHandlerThread.getLooper(), componentName); mPackageManager = mContext.getPackageManager(); if (mPackageManager != null) { mChangedPackages = mPackageManager.getChangedPackages(mLastSequenceNumber); @@ -351,6 +387,7 @@ public class ImsServiceController { mContext = context; mComponentName = componentName; mCallbacks = callbacks; + mHandler = handler; mBackoff = new ExponentialBackoff( rebindRetry.getStartDelay(), rebindRetry.getMaximumDelay(), @@ -359,6 +396,7 @@ public class ImsServiceController { mRestartImsServiceRunnable); mPermissionManager = null; mRepo = repo; + mImsEnablementTracker = new ImsEnablementTracker(handler.getLooper(), componentName); } /** @@ -378,6 +416,8 @@ public class ImsServiceController { sanitizeFeatureConfig(imsFeatureSet); mImsFeatures = imsFeatureSet; mSlotIdToSubIdMap = slotIdToSubIdMap; + // Set the number of slots that support the feature + mImsEnablementTracker.setNumOfSlots(mSlotIdToSubIdMap.size()); grantPermissionsToService(); Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent( mComponentName); @@ -464,6 +504,14 @@ public class ImsServiceController { + newSubId); Log.i(LOG_TAG, "subId changed for slot: " + slotID + ", " + oldSubId + " -> " + newSubId); + if (newSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + /* An INVALID subId can also be set in bind(), however + the ImsEnablementTracker will move into the DEFAULT state, so we only + need to track changes in subId that result in requiring we move + the state machine back to DEFAULT. + */ + mImsEnablementTracker.subIdChangedToInvalid(slotID); + } } } mSlotIdToSubIdMap = slotIdToSubIdMap; @@ -553,15 +601,7 @@ public class ImsServiceController { * trigger ImsFeature status updates. */ public void enableIms(int slotId, int subId) { - try { - synchronized (mLock) { - if (isServiceControllerAvailable()) { - mIImsServiceController.enableIms(slotId, subId); - } - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage()); - } + mImsEnablementTracker.enableIms(slotId, subId); } /** @@ -569,15 +609,15 @@ public class ImsServiceController { * trigger ImsFeature capability status to become false. */ public void disableIms(int slotId, int subId) { - try { - synchronized (mLock) { - if (isServiceControllerAvailable()) { - mIImsServiceController.disableIms(slotId, subId); - } - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage()); - } + mImsEnablementTracker.disableIms(slotId, subId); + } + + /** + * Notify ImsService to disable IMS for the framework. + * And notify ImsService back to enable IMS for the framework + */ + public void resetIms(int slotId, int subId) { + mImsEnablementTracker.resetIms(slotId, subId); } /** @@ -651,6 +691,7 @@ public class ImsServiceController { */ protected void setServiceController(IBinder serviceController) { mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController); + mImsEnablementTracker.setServiceController(serviceController); } /** @@ -910,7 +951,7 @@ public class ImsServiceController { return; } ChangedPackages curChangedPackages = - mPackageManager.getChangedPackages(mLastSequenceNumber); + mPackageManager.getChangedPackages(mLastSequenceNumber); if (curChangedPackages != null) { mLastSequenceNumber = curChangedPackages.getSequenceNumber(); List packagesNames = curChangedPackages.getPackageNames(); diff --git a/src/java/com/android/internal/telephony/imsphone/ImsCallInfo.java b/src/java/com/android/internal/telephony/imsphone/ImsCallInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..79ab9c5b55cb565bde8bce1604b2f67f8c39e30c --- /dev/null +++ b/src/java/com/android/internal/telephony/imsphone/ImsCallInfo.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.imsphone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.telephony.AccessNetworkConstants; +import android.telephony.ServiceState; + +import com.android.internal.telephony.Call; + +/** + * Contains call state to be notified to modem. + */ +public class ImsCallInfo { + + private final int mIndex; + private @Nullable ImsPhoneConnection mConnection = null; + private Call.State mState = Call.State.IDLE; + private boolean mIsHeldByRemote = false; + + public ImsCallInfo(int index) { + mIndex = index; + } + + /** Clears the call state. */ + public void reset() { + mConnection = null; + mState = Call.State.IDLE; + mIsHeldByRemote = false; + } + + /** + * Updates the state of the IMS call. + * + * @param c The instance of {@link ImsPhoneConnection}. + */ + public void update(@NonNull ImsPhoneConnection c) { + mConnection = c; + mState = c.getState(); + } + + /** + * Updates the state of the IMS call. + * + * @param c The instance of {@link ImsPhoneConnection}. + * @param holdReceived {@code true} if the remote party held the call. + * @param resumeReceived {@code true} if the remote party resumed the call. + */ + public boolean update(@NonNull ImsPhoneConnection c, + boolean holdReceived, boolean resumeReceived) { + Call.State state = c.getState(); + boolean changed = mState != state; + mState = state; + + if (holdReceived && !mIsHeldByRemote) { + changed = true; + mIsHeldByRemote = true; + } else if (resumeReceived && mIsHeldByRemote) { + changed = true; + mIsHeldByRemote = false; + } + + return changed; + } + + /** Called when clearing orphaned connection. */ + public void onDisconnect() { + mState = Call.State.DISCONNECTED; + } + + /** @return the call index. */ + public int getIndex() { + return mIndex; + } + + /** @return the call state. */ + public Call.State getCallState() { + return mState; + } + + /** @return whether the remote party is holding the call. */ + public boolean isHeldByRemote() { + return mIsHeldByRemote; + } + + /** @return {@code true} if the call is an incoming call. */ + public boolean isIncoming() { + return mConnection.isIncoming(); + } + + /** @return {@code true} if the call is an emergency call. */ + public boolean isEmergencyCall() { + return mConnection.isEmergencyCall(); + } + + /** @return the radio technology used for current connection. */ + public @AccessNetworkConstants.RadioAccessNetworkType int getCallRadioTech() { + return ServiceState.rilRadioTechnologyToAccessNetworkType(mConnection.getCallRadioTech()); + } + + @Override + public String toString() { + return "[ id=" + mIndex + ", state=" + mState + + ", isMT=" + isIncoming() + ", heldByRemote=" + mIsHeldByRemote + " ]"; + } +} diff --git a/src/java/com/android/internal/telephony/imsphone/ImsCallInfoTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsCallInfoTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..5783e489b4085747aa4178fe2384c073912515ba --- /dev/null +++ b/src/java/com/android/internal/telephony/imsphone/ImsCallInfoTracker.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.imsphone; + +import static com.android.internal.telephony.Call.State.DISCONNECTED; +import static com.android.internal.telephony.Call.State.IDLE; + +import android.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Call; +import com.android.internal.telephony.Connection; +import com.android.internal.telephony.Phone; +import com.android.telephony.Rlog; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Contains the state of all IMS calls. + */ +public class ImsCallInfoTracker { + private static final String LOG_TAG = "ImsCallInfoTracker"; + private static final boolean DBG = false; + + private final Phone mPhone; + private final List mQueue = new ArrayList<>(); + private int mNextIndex = 1; + + private final Map mImsCallInfo = new HashMap<>(); + + public ImsCallInfoTracker(Phone phone) { + mPhone = phone; + } + + /** + * Adds a new instance of the IMS call. + * + * @param c The instance of {@link ImsPhoneConnection}. + */ + public void addImsCallStatus(@NonNull ImsPhoneConnection c) { + if (DBG) Rlog.d(LOG_TAG, "addImsCallStatus"); + + synchronized (mImsCallInfo) { + if (mQueue.isEmpty()) { + mQueue.add(new ImsCallInfo(mNextIndex++)); + } + + Iterator it = mQueue.iterator(); + ImsCallInfo imsCallInfo = it.next(); + mQueue.remove(imsCallInfo); + + imsCallInfo.update(c); + mImsCallInfo.put(c, imsCallInfo); + + notifyImsCallStatus(); + + if (DBG) dump(); + } + } + + /** + * Updates the list of IMS calls. + * + * @param c The instance of {@link ImsPhoneConnection}. + */ + public void updateImsCallStatus(@NonNull ImsPhoneConnection c) { + updateImsCallStatus(c, false, false); + } + + /** + * Updates the list of IMS calls. + * + * @param c The instance of {@link ImsPhoneConnection}. + * @param holdReceived {@code true} if the remote party held the call. + * @param resumeReceived {@code true} if the remote party resumed the call. + */ + public void updateImsCallStatus(@NonNull ImsPhoneConnection c, + boolean holdReceived, boolean resumeReceived) { + if (DBG) { + Rlog.d(LOG_TAG, "updateImsCallStatus holdReceived=" + holdReceived + + ", resumeReceived=" + resumeReceived); + } + + synchronized (mImsCallInfo) { + ImsCallInfo info = mImsCallInfo.get(c); + + if (info == null) { + // This happens when the user tries to hangup the call after handover has completed. + return; + } + + boolean changed = info.update(c, holdReceived, resumeReceived); + + if (changed) notifyImsCallStatus(); + + Call.State state = c.getState(); + + if (DBG) Rlog.d(LOG_TAG, "updateImsCallStatus state=" + state); + // Call is disconnected. There are 2 cases in disconnected state: + // if silent redial, state == IDLE, otherwise, state == DISCONNECTED. + if (state == DISCONNECTED || state == IDLE) { + // clear the disconnected call + mImsCallInfo.remove(c); + info.reset(); + if (info.getIndex() < (mNextIndex - 1)) { + mQueue.add(info); + sort(mQueue); + } else { + mNextIndex--; + } + } + + if (DBG) dump(); + } + } + + /** Clears all orphaned IMS call information. */ + public void clearAllOrphanedConnections() { + if (DBG) Rlog.d(LOG_TAG, "clearAllOrphanedConnections"); + + Collection infos = mImsCallInfo.values(); + infos.stream().forEach(info -> { info.onDisconnect(); }); + notifyImsCallStatus(); + clearAllCallInfo(); + + if (DBG) dump(); + } + + /** Notifies that SRVCC has completed. */ + public void notifySrvccCompleted() { + if (DBG) Rlog.d(LOG_TAG, "notifySrvccCompleted"); + + clearAllCallInfo(); + notifyImsCallStatus(); + + if (DBG) dump(); + } + + private void clearAllCallInfo() { + try { + Collection infos = mImsCallInfo.values(); + infos.stream().forEach(info -> { info.reset(); }); + mImsCallInfo.clear(); + mQueue.clear(); + mNextIndex = 1; + } catch (UnsupportedOperationException e) { + Rlog.e(LOG_TAG, "e=" + e); + } + } + + private void notifyImsCallStatus() { + Collection infos = mImsCallInfo.values(); + ArrayList imsCallInfo = new ArrayList(infos); + sort(imsCallInfo); + mPhone.updateImsCallStatus(imsCallInfo, null); + } + + /** + * Sorts the list of IMS calls by the call index. + * + * @param infos The list of IMS calls. + */ + @VisibleForTesting + public static void sort(List infos) { + Collections.sort(infos, new Comparator() { + @Override + public int compare(ImsCallInfo l, ImsCallInfo r) { + if (l.getIndex() > r.getIndex()) { + return 1; + } else if (l.getIndex() < r.getIndex()) { + return -1; + } + return 0; + } + }); + } + + private void dump() { + Collection infos = mImsCallInfo.values(); + ArrayList imsCallInfo = new ArrayList(infos); + sort(imsCallInfo); + Rlog.d(LOG_TAG, "imsCallInfos=" + imsCallInfo); + } +} diff --git a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..3dedde83bef42ccdc5b88fa3d95c7b2c0359d9f8 --- /dev/null +++ b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java @@ -0,0 +1,355 @@ +/* + * 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.imsphone; + +import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA; +import static android.telephony.CarrierConfigManager.Ims.KEY_NR_SA_DISABLE_POLICY_INT; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_NONE; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED; +import static android.telephony.CarrierConfigManager.Ims.NrSaDisablePolicy; +import static android.telephony.CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY; +import static android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Call; + +import java.util.Arrays; +import java.util.Set; + +/** + * Enables or Disables NR-SA mode temporarily under certain conditions where WFC is established or + * IMS is registered over WiFi in order to improve the delay or voice mute issue when the handover + * from ePDG to NR is not supported in UE or network. + */ +public class ImsNrSaModeHandler extends Handler{ + + public static final String TAG = "ImsNrSaModeHandler"; + public static final String MMTEL_FEATURE_TAG = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\""; + + private static final int MSG_PRECISE_CALL_STATE_CHANGED = 101; + private static final int MSG_REQUEST_IS_VONR_ENABLED = 102; + private static final int MSG_RESULT_IS_VONR_ENABLED = 103; + + private final @NonNull ImsPhone mPhone; + private @Nullable CarrierConfigManager mCarrierConfigManager; + + private @NrSaDisablePolicy int mNrSaDisablePolicy; + private boolean mIsNrSaDisabledForWfc; + private boolean mIsVowifiRegistered; + private boolean mIsInImsCall; + private boolean mIsNrSaSupported; + + private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener = + (slotIndex, subId, carrierId, specificCarrierId) -> setNrSaDisablePolicy(subId); + + public ImsNrSaModeHandler(@NonNull ImsPhone phone, Looper looper) { + super(looper); + mPhone = phone; + mCarrierConfigManager = (CarrierConfigManager) mPhone.getContext() + .getSystemService(Context.CARRIER_CONFIG_SERVICE); + + registerForCarrierConfigChanges(); + } + + /** + * Performs any cleanup required before the ImsNrSaModeHandler is destroyed. + */ + public void tearDown() { + unregisterForCarrierConfigChanges(); + unregisterForPreciseCallStateChanges(); + + if (isNrSaDisabledForWfc()) { + setNrSaMode(true); + } + } + + /** + * Based on changed VoWiFi reg state and call state, handles NR SA mode if needed. + * It is including handover case. + * + * @param imsRadioTech The current registered RAT. + */ + public void onImsRegistered( + @ImsRegistrationTech int imsRadioTech, @NonNull Set featureTags) { + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE) { + return; + } + + Log.d(TAG, "onImsRegistered: ImsRegistrationTech = " + imsRadioTech); + + boolean isVowifiRegChanged = false; + + if (isVowifiRegistered() && imsRadioTech != REGISTRATION_TECH_IWLAN) { + setVowifiRegStatus(false); + isVowifiRegChanged = true; + } else if (!isVowifiRegistered() && imsRadioTech == REGISTRATION_TECH_IWLAN + && featureTags.contains(MMTEL_FEATURE_TAG)) { + setVowifiRegStatus(true); + isVowifiRegChanged = true; + } + + if (isVowifiRegChanged) { + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED) { + setNrSaMode(!isVowifiRegistered()); + } else if ((mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED + || mNrSaDisablePolicy + == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) + && isImsCallOngoing()) { + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) { + requestIsVonrEnabled(!isVowifiRegistered()); + return; + } + + setNrSaMode(!isVowifiRegistered()); + } + } + } + + /** + * Based on changed VoWiFi reg state and call state, handles NR SA mode if needed. + * + * @param imsRadioTech The current un-registered RAT. + */ + public void onImsUnregistered( + @ImsRegistrationTech int imsRadioTech) { + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE + || imsRadioTech != REGISTRATION_TECH_IWLAN || !isVowifiRegistered()) { + return; + } + + Log.d(TAG, "onImsUnregistered : ImsRegistrationTech = " + imsRadioTech); + + setVowifiRegStatus(false); + + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED) { + setNrSaMode(true); + } else if ((mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED + || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) + && isImsCallOngoing()) { + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) { + requestIsVonrEnabled(true); + return; + } + + setNrSaMode(true); + } + } + + /** + * Based on changed precise call state and VoWiFi reg state, handles NR SA mode if needed. + */ + public void onPreciseCallStateChanged() { + Log.d(TAG, "onPreciseCallStateChanged : foreground state = " + + mPhone.getForegroundCall().getState() + ", background state = " + + mPhone.getBackgroundCall().getState()); + + boolean isImsCallStatusChanged = false; + + if (isImsCallJustEstablished()) { + setImsCallStatus(true); + isImsCallStatusChanged = true; + } else if (isImsCallJustTerminated()) { + setImsCallStatus(false); + isImsCallStatusChanged = true; + } + + if (isVowifiRegistered() && isImsCallStatusChanged) { + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) { + requestIsVonrEnabled(!isImsCallOngoing()); + return; + } + + setNrSaMode(!isImsCallOngoing()); + } + } + + @Override + public void handleMessage(Message msg) { + AsyncResult ar; + + switch (msg.what) { + case MSG_PRECISE_CALL_STATE_CHANGED : + onPreciseCallStateChanged(); + break; + case MSG_REQUEST_IS_VONR_ENABLED : + Log.d(TAG, "request isVoNrEnabled"); + mPhone.getDefaultPhone().mCi.isVoNrEnabled( + obtainMessage(MSG_RESULT_IS_VONR_ENABLED, msg.obj), null); + break; + case MSG_RESULT_IS_VONR_ENABLED : + ar = (AsyncResult) msg.obj; + + if (ar.result != null) { + boolean vonrEnabled = ((Boolean) ar.result).booleanValue(); + + Log.d(TAG, "result isVoNrEnabled = " + vonrEnabled); + if (!vonrEnabled) { + setNrSaMode(((Boolean) ar.userObj).booleanValue()); + } + } + + ar = null; + break; + default : + break; + } + } + + /** + * Registers for precise call state changes. + */ + private void registerForPreciseCallStateChanges() { + mPhone.registerForPreciseCallStateChanged(this, MSG_PRECISE_CALL_STATE_CHANGED, null); + } + + /** + * Unregisters for precise call state changes. + */ + private void unregisterForPreciseCallStateChanges() { + mPhone.unregisterForPreciseCallStateChanged(this); + } + + /** + * Registers for carrier config changes. + */ + private void registerForCarrierConfigChanges() { + if (mCarrierConfigManager != null) { + mCarrierConfigManager.registerCarrierConfigChangeListener( + this::post, mCarrierConfigChangeListener); + } + } + + /** + * Unregisters for carrier config changes. + */ + private void unregisterForCarrierConfigChanges() { + if (mCarrierConfigManager != null) { + mCarrierConfigManager.unregisterCarrierConfigChangeListener( + mCarrierConfigChangeListener); + } + } + + private void setNrSaDisablePolicy(int subId) { + if (mPhone.getSubId() == subId && mCarrierConfigManager != null) { + PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(), + KEY_NR_SA_DISABLE_POLICY_INT, KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY); + mNrSaDisablePolicy = bundle.getInt(KEY_NR_SA_DISABLE_POLICY_INT); + mIsNrSaSupported = Arrays.stream( + bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)).anyMatch( + value -> value == CARRIER_NR_AVAILABILITY_SA); + + Log.d(TAG, "setNrSaDisablePolicy : NrSaDisablePolicy = " + + mNrSaDisablePolicy + ", IsNrSaSupported = " + mIsNrSaSupported); + + if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED + || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED) { + registerForPreciseCallStateChanges(); + } else { + unregisterForPreciseCallStateChanges(); + } + } + } + + private void requestIsVonrEnabled(boolean onOrOff) { + Message msg = obtainMessage(MSG_REQUEST_IS_VONR_ENABLED, onOrOff); + msg.sendToTarget(); + } + + private void setNrSaMode(boolean onOrOff) { + if (mIsNrSaSupported) { + mPhone.getDefaultPhone().mCi.setN1ModeEnabled(onOrOff, null); + Log.i(TAG, "setNrSaMode : " + onOrOff); + + setNrSaDisabledForWfc(!onOrOff); + } + } + + /** + * Sets VoWiFi reg status. + */ + @VisibleForTesting + public void setVowifiRegStatus(boolean registered) { + Log.d(TAG, "setVowifiRegStatus : " + registered); + mIsVowifiRegistered = registered; + } + + /** + * Sets IMS call status + */ + @VisibleForTesting + public void setImsCallStatus(boolean inImsCall) { + Log.d(TAG, "setImsCallStatus : " + inImsCall); + mIsInImsCall = inImsCall; + } + + @VisibleForTesting + public boolean isVowifiRegistered() { + return mIsVowifiRegistered; + } + + @VisibleForTesting + public boolean isImsCallOngoing() { + return mIsInImsCall; + } + + @VisibleForTesting + public boolean isNrSaDisabledForWfc() { + return mIsNrSaDisabledForWfc; + } + + @VisibleForTesting + public void setNrSaDisabledForWfc(boolean disabled) { + mIsNrSaDisabledForWfc = disabled; + } + + private boolean isImsCallJustEstablished() { + if (!isImsCallOngoing()) { + if ((mPhone.getForegroundCall().getState() == Call.State.ACTIVE) + || (mPhone.getBackgroundCall().getState() == Call.State.ACTIVE)) { + Log.d(TAG, "isImsCallJustEstablished"); + return true; + } + } + + return false; + } + + private boolean isImsCallJustTerminated() { + if (isImsCallOngoing() && (!mPhone.getForegroundCall().getState().isAlive() + && !mPhone.getBackgroundCall().getState().isAlive())) { + Log.d(TAG, "isImsCallJustTerminated"); + return true; + } + + return false; + } +} diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java index 5c14b0ebf5e5227a0c228110f9b4f4bd257121c4..4b150cc533dd334348965db037b8dfbaffff65b8 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java @@ -16,9 +16,14 @@ package com.android.internal.telephony.imsphone; -import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE; +import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED; +import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE; import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC; import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAICr; @@ -42,6 +47,7 @@ import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDI import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE; import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE; +import android.annotation.NonNull; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; @@ -71,7 +77,6 @@ import android.telephony.CarrierConfigManager; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneNumberUtils; import android.telephony.ServiceState; -import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UssdResponse; @@ -79,9 +84,12 @@ import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsCallForwardInfo; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.ImsSsData; import android.telephony.ims.ImsSsInfo; import android.telephony.ims.RegistrationManager; +import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsUtImplBase; import android.text.TextUtils; import android.util.LocalLog; @@ -107,10 +115,11 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneNotifier; import com.android.internal.telephony.ServiceStateTracker; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyComponentFactory; import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; import com.android.internal.telephony.emergency.EmergencyNumberTracker; +import com.android.internal.telephony.emergency.EmergencyStateTracker; import com.android.internal.telephony.gsm.SuppServiceNotification; import com.android.internal.telephony.metrics.ImsStats; import com.android.internal.telephony.metrics.TelephonyMetrics; @@ -184,6 +193,7 @@ public class ImsPhone extends ImsPhoneBase { return new ImsDialArgs.Builder() .setUusInfo(dialArgs.uusInfo) .setIsEmergency(dialArgs.isEmergency) + .setEccCategory(dialArgs.eccCategory) .setVideoState(dialArgs.videoState) .setIntentExtras(dialArgs.intentExtras) .setRttTextStream(((ImsDialArgs)dialArgs).rttTextStream) @@ -253,6 +263,7 @@ public class ImsPhone extends ImsPhoneBase { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) ImsPhoneCallTracker mCT; ImsExternalCallTracker mExternalCallTracker; + ImsNrSaModeHandler mImsNrSaModeHandler; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private ArrayList mPendingMMIs = new ArrayList(); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -289,6 +300,16 @@ public class ImsPhone extends ImsPhoneBase { private ImsStats mImsStats; + private int mImsRegistrationState; + // The access network type where IMS is registered + private @ImsRegistrationImplBase.ImsRegistrationTech int mImsRegistrationTech = + REGISTRATION_TECH_NONE; + private @RegistrationManager.SuggestedAction int mImsRegistrationSuggestedAction; + private @ImsRegistrationImplBase.ImsRegistrationTech int mImsDeregistrationTech = + REGISTRATION_TECH_NONE; + private int mImsRegistrationCapabilities; + private boolean mNotifiedRegisteredState; + // A runnable which is used to automatically exit from Ecm after a period of time. private Runnable mExitEcmRunnable = new Runnable() { @Override @@ -453,16 +474,16 @@ public class ImsPhone extends ImsPhoneBase { TelephonyComponentFactory.getInstance() .inject(ImsExternalCallTracker.class.getName()) .makeImsExternalCallTracker(this); + mImsNrSaModeHandler = + TelephonyComponentFactory.getInstance() + .inject(ImsNrSaModeHandler.class.getName()) + .makeImsNrSaModeHandler(this); mCT = TelephonyComponentFactory.getInstance().inject(ImsPhoneCallTracker.class.getName()) .makeImsPhoneCallTracker(this); mCT.registerPhoneStateListener(mExternalCallTracker); mExternalCallTracker.setCallPuller(mCT); - boolean legacyMode = true; - if (mDefaultPhone.getAccessNetworksManager() != null) { - legacyMode = mDefaultPhone.getAccessNetworksManager().isInLegacyMode(); - } - mSS.setOutOfService(legacyMode, false); + mSS.setOutOfService(false); mPhoneId = mDefaultPhone.getPhoneId(); @@ -504,6 +525,7 @@ public class ImsPhone extends ImsPhoneBase { //super.dispose(); mPendingMMIs.clear(); mExternalCallTracker.tearDown(); + mImsNrSaModeHandler.tearDown(); mCT.unregisterPhoneStateListener(mExternalCallTracker); mCT.unregisterForVoiceCallEnded(this); mCT.dispose(); @@ -892,6 +914,9 @@ public class ImsPhone extends ImsPhoneBase { @Override public boolean isInImsEcm() { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + return EmergencyStateTracker.getInstance().isInImsEcm(); + } return mIsInImsEcm; } @@ -1584,7 +1609,7 @@ public class ImsPhone extends ImsPhoneBase { } @Override - public void notifySrvccState(Call.SrvccState state) { + public void notifySrvccState(int state) { mCT.notifySrvccState(state); } @@ -2038,6 +2063,10 @@ public class ImsPhone extends ImsPhoneBase { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void handleEnterEmergencyCallbackMode() { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + logd("DomainSelection enabled: ignore ECBM enter event."); + return; + } if (DBG) logd("handleEnterEmergencyCallbackMode,mIsPhoneInEcmState= " + isInEcm()); // if phone is not in Ecm mode, and it's changed to Ecm mode if (!isInEcm()) { @@ -2059,6 +2088,10 @@ public class ImsPhone extends ImsPhoneBase { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Override protected void handleExitEmergencyCallbackMode() { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + logd("DomainSelection enabled: ignore ECBM exit event."); + return; + } if (DBG) logd("handleExitEmergencyCallbackMode: mIsPhoneInEcmState = " + isInEcm()); if (isInEcm()) { @@ -2088,6 +2121,7 @@ public class ImsPhone extends ImsPhoneBase { * Ecm timer and notify apps the timer is restarted. */ void handleTimerInEmergencyCallbackMode(int action) { + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) return; switch (action) { case CANCEL_ECM_TIMER: removeCallbacks(mExitEcmRunnable); @@ -2371,7 +2405,7 @@ public class ImsPhone extends ImsPhoneBase { /** * Update roaming state and WFC mode in the following situations: * 1) voice is in service. - * 2) data is in service and it is not IWLAN (if in legacy mode). + * 2) data is in service. * @param ss non-null ServiceState */ private void updateRoamingState(ServiceState ss) { @@ -2392,15 +2426,7 @@ public class ImsPhone extends ImsPhoneBase { logi("updateRoamingState: we are not IN_SERVICE, ignoring roaming change."); return; } - // We ignore roaming changes when moving to IWLAN because it always sets the roaming - // mode to home and masks the actual cellular roaming status if voice is not registered. If - // we just moved to IWLAN because WFC roaming mode is IWLAN preferred and WFC home mode is - // cell preferred, we can get into a condition where the modem keeps bouncing between - // IWLAN->cell->IWLAN->cell... - if (isCsNotInServiceAndPsWwanReportingWlan(ss)) { - logi("updateRoamingState: IWLAN masking roaming, ignore roaming change."); - return; - } + if (mCT.getState() == PhoneConstants.State.IDLE) { if (DBG) logd("updateRoamingState now: " + newRoamingState); mLastKnownRoamingState = newRoamingState; @@ -2419,30 +2445,6 @@ public class ImsPhone extends ImsPhoneBase { } } - /** - * In legacy mode, data registration will report IWLAN when we are using WLAN for data, - * effectively masking the true roaming state of the device if voice is not registered. - * - * @return true if we are reporting not in service for CS domain over WWAN transport and WLAN - * for PS domain over WWAN transport. - */ - private boolean isCsNotInServiceAndPsWwanReportingWlan(ServiceState ss) { - // We can not get into this condition if we are in AP-Assisted mode. - if (mDefaultPhone.getAccessNetworksManager() == null - || !mDefaultPhone.getAccessNetworksManager().isInLegacyMode()) { - return false; - } - NetworkRegistrationInfo csInfo = ss.getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - NetworkRegistrationInfo psInfo = ss.getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - // We will return roaming state correctly if the CS domain is in service because - // ss.getRoaming() returns isVoiceRoaming||isDataRoaming result and isDataRoaming==false - // when the modem reports IWLAN RAT. - return psInfo != null && csInfo != null && !csInfo.isInService() - && psInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN; - } - public RegistrationManager.RegistrationCallback getImsMmTelRegistrationCallback() { return mImsMmTelRegistrationHelper.getCallback(); } @@ -2453,12 +2455,18 @@ public class ImsPhone extends ImsPhoneBase { public void resetImsRegistrationState() { if (DBG) logd("resetImsRegistrationState"); mImsMmTelRegistrationHelper.reset(); + int subId = getSubId(); + if (SubscriptionManager.isValidSubscriptionId(subId)) { + updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED, + REGISTRATION_TECH_NONE, SUGGESTED_ACTION_NONE); + } } private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mMmTelRegistrationUpdate = new ImsRegistrationCallbackHelper.ImsRegistrationUpdate() { @Override - public void handleImsRegistered(int imsRadioTech) { + public void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes) { + int imsRadioTech = attributes.getTransportType(); if (DBG) { logd("handleImsRegistered: onImsMmTelConnected imsRadioTech=" + AccessNetworkConstants.transportTypeToString(imsRadioTech)); @@ -2469,6 +2477,10 @@ public class ImsPhone extends ImsPhoneBase { getDefaultPhone().setImsRegistrationState(true); mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.CONNECTED, null); mImsStats.onImsRegistered(imsRadioTech); + mImsNrSaModeHandler.onImsRegistered( + attributes.getRegistrationTechnology(), attributes.getFeatureTags()); + updateImsRegistrationInfo(REGISTRATION_STATE_REGISTERED, + attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE); } @Override @@ -2487,10 +2499,13 @@ public class ImsPhone extends ImsPhoneBase { } @Override - public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) { + public void handleImsUnregistered(ImsReasonInfo imsReasonInfo, + @RegistrationManager.SuggestedAction int suggestedAction, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { if (DBG) { logd("handleImsUnregistered: onImsMmTelDisconnected imsReasonInfo=" - + imsReasonInfo); + + imsReasonInfo + ", suggestedAction=" + suggestedAction + + ", disconnectedRadioTech=" + imsRadioTech); } mRegLocalLog.log("handleImsUnregistered: onImsMmTelDisconnected imsRadioTech=" + imsReasonInfo); @@ -2500,6 +2515,17 @@ public class ImsPhone extends ImsPhoneBase { mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.DISCONNECTED, imsReasonInfo); mImsStats.onImsUnregistered(imsReasonInfo); + mImsNrSaModeHandler.onImsUnregistered(imsRadioTech); + mImsRegistrationTech = REGISTRATION_TECH_NONE; + int suggestedModemAction = SUGGESTED_ACTION_NONE; + if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_REGISTRATION_ERROR) { + if ((suggestedAction == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK) + || (suggestedAction == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT)) { + suggestedModemAction = suggestedAction; + } + } + updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED, + imsRadioTech, suggestedModemAction); } @Override @@ -2520,44 +2546,24 @@ public class ImsPhone extends ImsPhoneBase { int subId = getSubId(); if (!SubscriptionManager.isValidSubscriptionId(subId)) { // Defending b/219080264: - // SubscriptionController.setSubscriptionProperty validates input subId + // SubscriptionManagerService.setSubscriptionProperty validates input subId // so do not proceed if subId invalid. This may be happening because cached // IMS callbacks are sent back to telephony after SIM state changed. return; } - if (isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = mSubscriptionManagerService - .getSubscriptionInfoInternal(subId); - if (subInfo != null) { - phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, - subInfo.getCountryIso()); - if (phoneNumber == null) { - return; - } - mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber); - } - } else { - SubscriptionController subController = SubscriptionController.getInstance(); - String countryIso = getCountryIso(subController, subId); - // Format the number as one more defense to reject garbage values: - // phoneNumber will become null. - phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, countryIso); + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + if (subInfo != null) { + phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, + subInfo.getCountryIso()); if (phoneNumber == null) { return; } - subController.setSubscriptionProperty(subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, - phoneNumber); + mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber); } } - private static String getCountryIso(SubscriptionController subController, int subId) { - SubscriptionInfo info = subController.getSubscriptionInfo(subId); - String countryIso = info == null ? "" : info.getCountryIso(); - // info.getCountryIso() may return null - return countryIso == null ? "" : countryIso; - } - /** * Finds the phone number from associated URIs. * @@ -2632,6 +2638,131 @@ public class ImsPhone extends ImsPhoneBase { return mLastKnownRoamingState; } + /** + * Update IMS registration information to modem. + * + * @param capabilities indicate MMTEL capability such as VOICE, VIDEO and SMS. + */ + public void updateImsRegistrationInfo(int capabilities) { + if (mImsRegistrationState == REGISTRATION_STATE_REGISTERED) { + if (mNotifiedRegisteredState && (capabilities == mImsRegistrationCapabilities)) { + // Duplicated notification, no change in capabilities. + return; + } + + mImsRegistrationCapabilities = capabilities; + if (capabilities == 0) { + // Ignore this as this usually happens just before onUnregistered callback. + // We can notify modem when onUnregistered() flow occurs. + return; + } + + mDefaultPhone.mCi.updateImsRegistrationInfo(mImsRegistrationState, + mImsRegistrationTech, 0, capabilities, null); + mNotifiedRegisteredState = true; + } + } + + /** + * Update IMS registration info + * + * @param regState indicates IMS registration state. + * @param imsRadioTech indicates the type of the radio access network where IMS is registered. + * @param suggestedAction indicates the suggested action for the radio to perform. + */ + private void updateImsRegistrationInfo( + @RegistrationManager.ImsRegistrationState int regState, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech, + @RegistrationManager.SuggestedAction int suggestedAction) { + + if (regState == mImsRegistrationState) { + if ((regState == REGISTRATION_STATE_REGISTERED && imsRadioTech == mImsRegistrationTech) + || (regState == REGISTRATION_STATE_NOT_REGISTERED + && suggestedAction == mImsRegistrationSuggestedAction + && imsRadioTech == mImsDeregistrationTech)) { + // Filter duplicate notification. + return; + } + } + + if (regState == REGISTRATION_STATE_NOT_REGISTERED) { + mDefaultPhone.mCi.updateImsRegistrationInfo(regState, + imsRadioTech, suggestedAction, 0, null); + } else if (mImsRegistrationState == REGISTRATION_STATE_REGISTERED) { + // This happens when radio tech is changed while in REGISTERED state. + if (mImsRegistrationCapabilities > 0) { + // Capability has been updated. Notify REGISTRATION_STATE_REGISTERED. + mDefaultPhone.mCi.updateImsRegistrationInfo(regState, imsRadioTech, 0, + mImsRegistrationCapabilities, null); + mImsRegistrationTech = imsRadioTech; + mNotifiedRegisteredState = true; + return; + } + } + + mImsRegistrationState = regState; + mImsRegistrationTech = imsRadioTech; + mImsRegistrationSuggestedAction = suggestedAction; + if (regState == REGISTRATION_STATE_NOT_REGISTERED) { + mImsDeregistrationTech = imsRadioTech; + } else { + mImsDeregistrationTech = REGISTRATION_TECH_NONE; + } + mImsRegistrationCapabilities = 0; + // REGISTRATION_STATE_REGISTERED will be notified when the capability is updated. + mNotifiedRegisteredState = false; + } + + @Override + public void setTerminalBasedCallWaitingStatus(int state) { + mCT.setTerminalBasedCallWaitingStatus(state); + } + + @Override + public void triggerEpsFallback(@MmTelFeature.EpsFallbackReason int reason, Message response) { + mDefaultPhone.triggerEpsFallback(reason, response); + } + + @Override + public void startImsTraffic(int token, + @MmTelFeature.ImsTrafficType int trafficType, + @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType, + @MmTelFeature.ImsTrafficDirection int trafficDirection, Message response) { + mDefaultPhone.startImsTraffic(token, trafficType, + accessNetworkType, trafficDirection, response); + } + + @Override + public void stopImsTraffic(int token, Message response) { + mDefaultPhone.stopImsTraffic(token, response); + } + + @Override + public void registerForConnectionSetupFailure(Handler h, int what, Object obj) { + mDefaultPhone.registerForConnectionSetupFailure(h, what, obj); + } + + @Override + public void unregisterForConnectionSetupFailure(Handler h) { + mDefaultPhone.unregisterForConnectionSetupFailure(h); + } + + @Override + public void triggerImsDeregistration( + @ImsRegistrationImplBase.ImsDeregistrationReason int reason) { + mCT.triggerImsDeregistration(reason); + } + + @Override + public void updateImsCallStatus(List imsCallInfo, Message response) { + mDefaultPhone.updateImsCallStatus(imsCallInfo, response); + } + + @Override + public void triggerNotifyAnbr(int mediaType, int direction, int bitsPerSecond) { + mCT.triggerNotifyAnbr(mediaType, direction, bitsPerSecond); + } + @Override public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java index b5c7da19258f1100271fcc3a4345c0908db13be0..8a1041dec37d8d7733de946664939cc85395810b 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java @@ -24,11 +24,13 @@ import android.os.RegistrantList; import android.sysprop.TelephonyProperties; import android.telephony.Annotation.DataActivityType; import android.telephony.CallQuality; +import android.telephony.CallState; import android.telephony.NetworkScanRequest; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.MediaQualityStatus; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; @@ -138,6 +140,10 @@ abstract class ImsPhoneBase extends Phone { mNotifier.notifyCallQualityChanged(this, callQuality, callNetworkType); } + public void onMediaQualityStatusChanged(MediaQualityStatus status) { + mNotifier.notifyMediaQualityStatusChanged(this, status); + } + @Override public ServiceState getServiceState() { // FIXME: we may need to provide this when data connectivity is lost @@ -192,7 +198,43 @@ abstract class ImsPhoneBase extends Phone { */ public void notifyPreciseCallStateChanged() { /* we'd love it if this was package-scoped*/ - super.notifyPreciseCallStateChangedP(); + AsyncResult ar = new AsyncResult(null, this, null); + mPreciseCallStateRegistrants.notifyRegistrants(ar); + + notifyPreciseCallStateToNotifier(); + } + + public void notifyPreciseCallStateToNotifier() { + ImsPhoneCall ringingCall = (ImsPhoneCall) getRingingCall(); + ImsPhoneCall foregroundCall = (ImsPhoneCall) getForegroundCall(); + ImsPhoneCall backgroundCall = (ImsPhoneCall) getBackgroundCall(); + + if (ringingCall != null && foregroundCall != null && backgroundCall != null) { + //Array for IMS call session ID of RINGING/FOREGROUND/BACKGROUND call + String[] imsCallIds = new String[CallState.CALL_CLASSIFICATION_MAX]; + //Array for IMS call service type of RINGING/FOREGROUND/BACKGROUND call + int[] imsCallServiceTypes = new int[CallState.CALL_CLASSIFICATION_MAX]; + //Array for IMS call type of RINGING/FOREGROUND/BACKGROUND call + int[] imsCallTypes = new int[CallState.CALL_CLASSIFICATION_MAX]; + imsCallIds[CallState.CALL_CLASSIFICATION_RINGING] = + ringingCall.getCallSessionId(); + imsCallIds[CallState.CALL_CLASSIFICATION_FOREGROUND] = + foregroundCall.getCallSessionId(); + imsCallIds[CallState.CALL_CLASSIFICATION_BACKGROUND] = + backgroundCall.getCallSessionId(); + imsCallServiceTypes[CallState.CALL_CLASSIFICATION_RINGING] = + ringingCall.getServiceType(); + imsCallServiceTypes[CallState.CALL_CLASSIFICATION_FOREGROUND] = + foregroundCall.getServiceType(); + imsCallServiceTypes[CallState.CALL_CLASSIFICATION_BACKGROUND] = + backgroundCall.getServiceType(); + imsCallTypes[CallState.CALL_CLASSIFICATION_RINGING] = ringingCall.getCallType(); + imsCallTypes[CallState.CALL_CLASSIFICATION_FOREGROUND] = + foregroundCall.getCallType(); + imsCallTypes[CallState.CALL_CLASSIFICATION_BACKGROUND] = + backgroundCall.getCallType(); + mNotifier.notifyPreciseCallState(this, imsCallIds, imsCallServiceTypes, imsCallTypes); + } } public void notifyDisconnect(Connection cn) { @@ -299,6 +341,11 @@ abstract class ImsPhoneBase extends Phone { return null; } + @Override + public int getImeiType() { + return Phone.IMEI_TYPE_UNKNOWN; + } + @Override public String getEsn() { Rlog.e(LOG_TAG, "[VoltePhone] getEsn() is a CDMA method"); @@ -530,4 +577,14 @@ abstract class ImsPhoneBase extends Phone { notifyPhoneStateChanged(); } } + + @Override + public int getTerminalBasedCallWaitingState(boolean forCsOnly) { + return getDefaultPhone().getTerminalBasedCallWaitingState(forCsOnly); + } + + @Override + public void setTerminalBasedCallWaitingSupported(boolean supported) { + getDefaultPhone().setTerminalBasedCallWaitingSupported(supported); + } } diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java index 98cc4418618cd2b1cb37402381cdae97bf8d813f..eaa6ab67001b742f0696adf35f8ae5b525987b90 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java @@ -19,6 +19,8 @@ package com.android.internal.telephony.imsphone; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.telephony.DisconnectCause; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsStreamMediaProfile; import android.util.Log; @@ -327,6 +329,41 @@ public class ImsPhoneCall extends Call { return (connection == null) ? null : connection.getImsCall(); } + /** + * Retrieves the {@link ImsCallSession#getCallId()} for the current {@link ImsPhoneCall}. + */ + @VisibleForTesting + public String getCallSessionId() { + return ((getImsCall() == null) ? null : getImsCall().getSession()) == null + ? null : getImsCall().getSession().getCallId(); + } + + /** + * Retrieves the service type in {@link ImsCallProfile} for the current {@link ImsPhoneCall}. + */ + @VisibleForTesting + public int getServiceType() { + if (getFirstConnection() == null) { + return ImsCallProfile.SERVICE_TYPE_NONE; + } else { + return getFirstConnection().isEmergencyCall() + ? ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL; + } + } + + /** + * Retrieves the call type in {@link ImsCallProfile} for the current {@link ImsPhoneCall}. + */ + @VisibleForTesting + public int getCallType() { + if (getImsCall() == null) { + return ImsCallProfile.CALL_TYPE_NONE; + } else { + return getImsCall().isVideoCall() + ? ImsCallProfile.CALL_TYPE_VT : ImsCallProfile.CALL_TYPE_VOICE; + } + } + /*package*/ static boolean isLocalTone(ImsCall imsCall) { if ((imsCall == null) || (imsCall.getCallProfile() == null) || (imsCall.getCallProfile().mMediaProfile == null)) { @@ -432,6 +469,15 @@ public class ImsPhoneCall extends Call { return mIsRingbackTonePlaying; } + public void maybeClearRemotelyHeldStatus() { + for (Connection conn : getConnections()) { + ImsPhoneConnection c = (ImsPhoneConnection) conn; + if (c.isHeldByRemote()) { + c.setRemotelyUnheld(); + } + } + } + private void takeOver(ImsPhoneCall that) { copyConnectionFrom(that); mState = that.mState; diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java index 7b19bc9a8a99aed4454eb0014103f92d7f813f08..c3ee0f6060be5874da58e642e5661aafe83d3cec 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java @@ -16,12 +16,32 @@ package com.android.internal.telephony.imsphone; +import static android.telephony.CarrierConfigManager.ImsVoice.ALERTING_SRVCC_SUPPORT; +import static android.telephony.CarrierConfigManager.ImsVoice.BASIC_SRVCC_SUPPORT; +import static android.telephony.CarrierConfigManager.ImsVoice.MIDCALL_SRVCC_SUPPORT; +import static android.telephony.CarrierConfigManager.ImsVoice.PREALERTING_SRVCC_SUPPORT; import static android.telephony.CarrierConfigManager.USSD_OVER_CS_PREFERRED; import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_ONLY; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DIALING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_HOLDING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING_SETUP; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_WAITING; +import static android.telephony.ims.ImsService.CAPABILITY_TERMINAL_BASED_CALL_WAITING; +import static android.telephony.ims.feature.ConnectionFailureInfo.REASON_UNSPECIFIED; +import static android.telephony.ims.feature.MmTelFeature.ImsTrafficSessionCallbackWrapper.INVALID_TOKEN; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_ACTIVATED; +import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED; +import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_SMS; +import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO; +import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE; import static com.android.internal.telephony.Phone.CS_FALLBACK; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.NetworkStatsManager; @@ -56,6 +76,7 @@ import android.sysprop.TelephonyProperties; import android.telecom.Connection.VideoProvider; import android.telecom.TelecomManager; import android.telecom.VideoProfile; +import android.telephony.AccessNetworkConstants; import android.telephony.CallQuality; import android.telephony.CarrierConfigManager; import android.telephony.DisconnectCause; @@ -74,9 +95,16 @@ import android.telephony.ims.ImsMmTelManager; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.telephony.ims.ImsSuppServiceNotification; +import android.telephony.ims.MediaQualityStatus; +import android.telephony.ims.MediaThreshold; import android.telephony.ims.ProvisioningManager; import android.telephony.ims.RtpHeaderExtension; import android.telephony.ims.RtpHeaderExtensionType; +import android.telephony.ims.SrvccCall; +import android.telephony.ims.aidl.IImsCallSessionListener; +import android.telephony.ims.aidl.IImsTrafficSessionCallback; +import android.telephony.ims.aidl.ISrvccStartedCallback; +import android.telephony.ims.feature.ConnectionFailureInfo; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -114,10 +142,12 @@ import com.android.internal.telephony.LocaleTracker; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.ServiceStateTracker; -import com.android.internal.telephony.SubscriptionController; +import com.android.internal.telephony.SrvccConnection; import com.android.internal.telephony.d2d.RtpTransport; import com.android.internal.telephony.data.DataSettingsManager; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; import com.android.internal.telephony.emergency.EmergencyNumberTracker; +import com.android.internal.telephony.emergency.EmergencyStateTracker; import com.android.internal.telephony.gsm.SuppServiceNotification; import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs; import com.android.internal.telephony.metrics.CallQualityMetrics; @@ -133,8 +163,10 @@ import com.android.telephony.Rlog; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -145,11 +177,14 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * {@hide} @@ -180,6 +215,20 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { SharedPreferences getDefaultSharedPreferences(Context context); } + private static class ImsTrafficSession { + private final @MmTelFeature.ImsTrafficType int mTrafficType; + private final @MmTelFeature.ImsTrafficDirection int mTrafficDirection; + private final @NonNull IImsTrafficSessionCallback mCallback; + + ImsTrafficSession(@MmTelFeature.ImsTrafficType int trafficType, + @MmTelFeature.ImsTrafficDirection int trafficDirection, + @NonNull IImsTrafficSessionCallback callback) { + mTrafficType = trafficType; + mTrafficDirection = trafficDirection; + mCallback = callback; + } + } + private static final boolean DBG = true; // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background @@ -210,16 +259,18 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { private final MmTelFeatureListener mMmTelFeatureListener = new MmTelFeatureListener(); private class MmTelFeatureListener extends MmTelFeature.Listener { - private void processIncomingCall(IImsCallSession c, Bundle extras) { + private IImsCallSessionListener processIncomingCall(@NonNull IImsCallSession c, + @Nullable String callId, @Nullable Bundle extras) { if (DBG) log("processIncomingCall: incoming call intent"); if (extras == null) extras = new Bundle(); - if (mImsManager == null) return; + if (mImsManager == null) return null; try { + IImsCallSessionListener iimsCallSessionListener; // Network initiated USSD will be treated by mImsUssdListener boolean isUssd = extras.getBoolean(MmTelFeature.EXTRA_IS_USSD, false); - // For compatibility purposes with older vendor implmentations. + // For compatibility purposes with older vendor implementations. isUssd |= extras.getBoolean(ImsManager.EXTRA_USSD, false); if (isUssd) { if (DBG) log("processIncomingCall: USSD"); @@ -228,11 +279,14 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { if (mUssdSession != null) { mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE); } - return; + if (callId != null) mUssdSession.getCallSession().setCallId(callId); + iimsCallSessionListener = (IImsCallSessionListener) mUssdSession + .getCallSession().getIImsCallSessionListenerProxy(); + return iimsCallSessionListener; } boolean isUnknown = extras.getBoolean(MmTelFeature.EXTRA_IS_UNKNOWN_CALL, false); - // For compatibility purposes with older vendor implmentations. + // For compatibility purposes with older vendor implementations. isUnknown |= extras.getBoolean(ImsManager.EXTRA_IS_UNKNOWN_CALL, false); if (DBG) { log("processIncomingCall: isUnknown = " + isUnknown @@ -242,6 +296,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { // Normal MT/Unknown call ImsCall imsCall = mImsManager.takeCall(c, mImsCallListener); + if (callId != null) imsCall.getCallSession().setCallId(callId); + iimsCallSessionListener = (IImsCallSessionListener) imsCall + .getCallSession().getIImsCallSessionListenerProxy(); ImsPhoneConnection conn = new ImsPhoneConnection(mPhone, imsCall, ImsPhoneCallTracker.this, (isUnknown ? mForegroundCall : mRingingCall), isUnknown); @@ -264,13 +321,13 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { if ((c != null) && (c.getCallProfile() != null) && (c.getCallProfile().getCallExtras() != null) && (c.getCallProfile().getCallExtras() - .containsKey(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE))) { + .containsKey(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE))) { String error = c.getCallProfile() .getCallExtra(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE, null); if (error != null) { try { int cause = getDisconnectCauseFromReasonInfo( - new ImsReasonInfo(Integer.parseInt(error), 0, null), + new ImsReasonInfo(Integer.parseInt(error), 0, null), conn.getState()); if (cause == DisconnectCause.INCOMING_AUTO_REJECTED) { conn.setDisconnectCause(cause); @@ -315,18 +372,20 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { updatePhoneState(); mPhone.notifyPreciseCallStateChanged(); + mImsCallInfoTracker.addImsCallStatus(conn); + return iimsCallSessionListener; } catch (ImsException | RemoteException e) { loge("processIncomingCall: exception " + e); mOperationLocalLog.log("onIncomingCall: exception processing: " + e); + return null; } } @Override - public void onIncomingCall(IImsCallSession c, Bundle extras) { - // we want to ensure we block this binder thread until incoming call setup completes - // as to avoid race conditions where the ImsService tries to update the state of the - // call before the listeners have been attached. - executeAndWait(()-> processIncomingCall(c, extras)); + @Nullable + public IImsCallSessionListener onIncomingCall( + @NonNull IImsCallSession c, @Nullable String callId, @Nullable Bundle extras) { + return executeAndWaitForReturn(()-> processIncomingCall(c, callId, extras)); } @Override @@ -341,6 +400,102 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { }, mExecutor); } + @Override + public void onAudioModeIsVoipChanged(int imsAudioHandler) { + TelephonyUtils.runWithCleanCallingIdentity(()-> { + ImsCall imsCall = null; + if (mForegroundCall.hasConnections()) { + imsCall = mForegroundCall.getImsCall(); + } else if (mBackgroundCall.hasConnections()) { + imsCall = mBackgroundCall.getImsCall(); + } else if (mRingingCall.hasConnections()) { + imsCall = mRingingCall.getImsCall(); + } else if (mHandoverCall.hasConnections()) { + imsCall = mHandoverCall.getImsCall(); + } else { + Rlog.e(LOG_TAG, "onAudioModeIsVoipChanged: no Call"); + } + + if (imsCall != null) { + ImsPhoneConnection conn = findConnection(imsCall); + if (conn != null) { + conn.onAudioModeIsVoipChanged(imsAudioHandler); + } + } else { + Rlog.e(LOG_TAG, "onAudioModeIsVoipChanged: no ImsCall"); + } + }, mExecutor); + } + + @Override + public void onTriggerEpsFallback(@MmTelFeature.EpsFallbackReason int reason) { + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (DBG) log("onTriggerEpsFallback reason=" + reason); + mPhone.triggerEpsFallback(reason, null); + }, mExecutor); + } + + @Override + public void onStartImsTrafficSession(int token, + @MmTelFeature.ImsTrafficType int trafficType, + @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType, + @MmTelFeature.ImsTrafficDirection int trafficDirection, + IImsTrafficSessionCallback callback) { + registerImsTrafficSession(token, trafficType, trafficDirection, callback); + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (DBG) { + log("onStartImsTrafficSession token=" + token + ", traffic=" + trafficType + + ", networkType=" + accessNetworkType + + ", direction=" + trafficDirection); + } + mPhone.startImsTraffic(token, trafficType, accessNetworkType, trafficDirection, + obtainMessage(EVENT_START_IMS_TRAFFIC_DONE, callback)); + }, mExecutor); + } + + @Override + public void onModifyImsTrafficSession(int token, + @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType) { + ImsTrafficSession session = getImsTrafficSession(token); + if (session == null) { + loge("onModifyImsTrafficSession unknown session, token=" + token); + return; + } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (DBG) { + log("onModifyImsTrafficSession token=" + token + + ", networkType=" + accessNetworkType); + } + mPhone.startImsTraffic(token, session.mTrafficType, + accessNetworkType, session.mTrafficDirection, + obtainMessage(EVENT_START_IMS_TRAFFIC_DONE, session.mCallback)); + }, mExecutor); + } + + @Override + public void onStopImsTrafficSession(int token) { + unregisterImsTrafficSession(token); + if (token == INVALID_TOKEN) return; + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (DBG) { + log("onStopImsTrafficSession token=" + token); + } + mPhone.stopImsTraffic(token, null); + }, mExecutor); + } + + @Override + public void onMediaQualityStatusChanged(MediaQualityStatus status) { + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mPhone != null && mPhone.mDefaultPhone != null) { + if (DBG) log("onMediaQualityStatusChanged " + status); + mPhone.onMediaQualityStatusChanged(status); + } else { + loge("onMediaQualityStatusChanged: null phone"); + } + }, mExecutor); + } + /** * Schedule the given Runnable on mExecutor and block this thread until it finishes. * @param r The Runnable to run. @@ -353,6 +508,24 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { logw("Binder - exception: " + e.getMessage()); } } + + /** + * Schedule the given Runnable on mExecutor and block this thread until it finishes. + * @param r The Runnable to run. + */ + private T executeAndWaitForReturn(Supplier r) { + + CompletableFuture future = CompletableFuture.supplyAsync( + () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor); + + try { + return future.get(); + } catch (ExecutionException | InterruptedException e) { + Log.w(LOG_TAG, "ImsPhoneCallTracker : executeAndWaitForReturn exception: " + + e.getMessage()); + return null; + } + } } /** @@ -488,6 +661,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { private static final int EVENT_ANSWER_WAITING_CALL = 30; private static final int EVENT_RESUME_NOW_FOREGROUND_CALL = 31; private static final int EVENT_REDIAL_WITHOUT_RTT = 32; + private static final int EVENT_START_IMS_TRAFFIC_DONE = 33; + private static final int EVENT_CONNECTION_SETUP_FAILURE = 34; + private static final int EVENT_NEW_ACTIVE_CALL_STARTED = 35; private static final int TIMEOUT_HANGUP_PENDINGMO = 500; @@ -559,6 +735,13 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } } + private class SrvccStartedCallback extends ISrvccStartedCallback.Stub { + @Override + public void onSrvccCallNotified(List profiles) { + handleSrvccConnectionInfo(profiles); + } + } + private volatile NetworkStats mVtDataUsageSnapshot = null; private volatile NetworkStats mVtDataUsageUidSnapshot = null; private final VtDataUsageProvider mVtDataUsageProvider = new VtDataUsageProvider(); @@ -613,14 +796,22 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { private boolean mSupportCepOnPeer = true; private boolean mSupportD2DUsingRtp = false; private boolean mSupportSdpForRtpHeaderExtensions = false; + private int mThresholdRtpPacketLoss; + private int mThresholdRtpJitter; + private long mThresholdRtpInactivityTime; + private final List mSrvccTypeSupported = new ArrayList<>(); + private final SrvccStartedCallback mSrvccStartedCallback = new SrvccStartedCallback(); // Tracks the state of our background/foreground calls while a call hold/swap operation is // in progress. Values listed above. private HoldSwapState mHoldSwitchingState = HoldSwapState.INACTIVE; + private MediaThreshold mMediaThreshold; private String mLastDialString = null; private ImsDialArgs mLastDialArgs = null; private Executor mExecutor = Runnable::run; + private final ImsCallInfoTracker mImsCallInfoTracker; + /** * Listeners to changes in the phone state. Intended for use by other interested IMS components * without the need to register a full blown {@link android.telephony.PhoneStateListener}. @@ -992,6 +1183,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { // Used for important operational related events for logging. private final LocalLog mOperationLocalLog = new LocalLog(64); + private final ConcurrentHashMap mImsTrafficSessions = + new ConcurrentHashMap<>(); + /** * Container to ease passing around a tuple of two objects. This object provides a sensible * implementation of equals(), returning true/false using equals() for one object (Integer) @@ -1040,6 +1234,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { return (first == null ? 0 : first.hashCode()); } } + //***** Events @@ -1111,10 +1306,15 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { postDelayed(mConnectorRunnable, CONNECTOR_RETRY_DELAY_MS); } stopListeningForCalls(); + stopAllImsTrafficTypes(); } }, executor); // It can take some time for ITelephony to get published, so defer connecting. post(mConnectorRunnable); + + mImsCallInfoTracker = new ImsCallInfoTracker(phone); + + mPhone.registerForConnectionSetupFailure(this, EVENT_CONNECTION_SETUP_FAILURE, null); } /** @@ -1191,6 +1391,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { // For compatibility with apps that still use deprecated intent sendImsServiceStateIntent(ImsManager.ACTION_IMS_SERVICE_UP); mCurrentlyConnectedSubId = Optional.of(subId); + + initializeTerminalBasedCallWaiting(); } /** @@ -1251,6 +1453,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { mUtInterface = null; } mCurrentlyConnectedSubId = Optional.empty(); + mMediaThreshold = null; resetImsCapabilities(); hangupAllOrphanedConnections(DisconnectCause.LOST_SIGNAL); // For compatibility with apps that still use deprecated intent @@ -1291,6 +1494,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { // above. Remove all references to it. mPendingMO = null; updatePhoneState(); + mImsCallInfoTracker.clearAllOrphanedConnections(); } /** @@ -1335,6 +1539,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { (NetworkStatsManager) mPhone.getContext().getSystemService( Context.NETWORK_STATS_SERVICE); statsManager.unregisterNetworkStatsProvider(mVtDataUsageProvider); + + mPhone.unregisterForConnectionSetupFailure(this); } @Override @@ -1526,7 +1732,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { mLastDialString = dialString; mLastDialArgs = dialArgs; mPendingMO = new ImsPhoneConnection(mPhone, dialString, this, mForegroundCall, - isEmergencyNumber, isWpsCall); + isEmergencyNumber, isWpsCall, dialArgs); mOperationLocalLog.log("dial requested. connId=" + System.identityHashCode(mPendingMO)); if (isEmergencyNumber && dialArgs != null && dialArgs.intentExtras != null) { Rlog.i(LOG_TAG, "dial ims emergency dialer: " + dialArgs.intentExtras.getBoolean( @@ -1543,9 +1749,22 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { addConnection(mPendingMO); if (!holdBeforeDial) { + // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit. if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) { dialInternal(mPendingMO, clirMode, videoState, dialArgs.retryCallFailCause, dialArgs.retryCallFailNetworkType, dialArgs.intentExtras); + } else if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + final int finalClirMode = clirMode; + final int finalVideoState = videoState; + Runnable onComplete = new Runnable() { + @Override + public void run() { + dialInternal(mPendingMO, finalClirMode, finalVideoState, + dialArgs.retryCallFailCause, dialArgs.retryCallFailNetworkType, + dialArgs.intentExtras); + } + }; + EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete); } else { try { getEcbmInterface().exitEmergencyCallbackMode(); @@ -1637,20 +1856,12 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { // Check for changes due to carrier config. maybeConfigureRtpHeaderExtensions(); - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(subId); - if (subInfo == null || !subInfo.isActive()) { - loge("updateCarrierConfiguration: skipping notification to ImsService, non" - + "active subId = " + subId); - return; - } - } else { - if (!SubscriptionController.getInstance().isActiveSubId(subId)) { - loge("updateCarrierConfiguration: skipping notification to ImsService, non" - + "active subId = " + subId); - return; - } + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(subId); + if (subInfo == null || !subInfo.isActive()) { + loge("updateCarrierConfiguration: skipping notification to ImsService, non" + + "active subId = " + subId); + return; } Phone defaultPhone = getPhone().getDefaultPhone(); @@ -1677,6 +1888,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { logi("updateCarrierConfiguration: Updating ImsService configs."); mCarrierConfigLoadedForSubscription = true; updateImsServiceConfig(); + updateMediaThreshold( + mThresholdRtpPacketLoss, mThresholdRtpJitter, mThresholdRtpInactivityTime); } /** @@ -1725,6 +1938,13 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { mSupportSdpForRtpHeaderExtensions = carrierConfig.getBoolean( CarrierConfigManager .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL); + mThresholdRtpPacketLoss = carrierConfig.getInt( + CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT); + mThresholdRtpInactivityTime = carrierConfig.getLong( + CarrierConfigManager.ImsVoice + .KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG); + mThresholdRtpJitter = carrierConfig.getInt( + CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT); if (mPhone.getContext().getResources().getBoolean( com.android.internal.R.bool.config_allow_ussd_over_ims)) { @@ -1771,6 +1991,41 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } else { log("No carrier ImsReasonInfo mappings defined."); } + + mSrvccTypeSupported.clear(); + int[] srvccType = + carrierConfig.getIntArray(CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY); + if (srvccType != null && srvccType.length > 0) { + mSrvccTypeSupported.addAll( + Arrays.stream(srvccType).boxed().collect(Collectors.toList())); + } + } + + private void updateMediaThreshold( + int thresholdPacketLoss, int thresholdJitter, long thresholdInactivityTime) { + if (!MediaThreshold.isValidRtpInactivityTimeMillis(thresholdInactivityTime) + && !MediaThreshold.isValidJitterMillis(thresholdJitter) + && !MediaThreshold.isValidRtpPacketLossRate(thresholdPacketLoss)) { + logi("There is no valid RTP threshold value"); + return; + } + int[] thPacketLosses = {thresholdPacketLoss}; + long[] thInactivityTimesMillis = {thresholdInactivityTime}; + int[] thJitters = {thresholdJitter}; + MediaThreshold threshold = new MediaThreshold.Builder() + .setThresholdsRtpPacketLossRate(thPacketLosses) + .setThresholdsRtpInactivityTimeMillis(thInactivityTimesMillis) + .setThresholdsRtpJitterMillis(thJitters).build(); + if (mMediaThreshold == null || !mMediaThreshold.equals(threshold)) { + logi("setMediaThreshold :" + threshold); + try { + mImsManager.setMediaThreshold(MediaQualityStatus.MEDIA_SESSION_TYPE_AUDIO, + threshold); + mMediaThreshold = threshold; + } catch (ImsException e) { + loge("setMediaThreshold Failed: " + e); + } + } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1873,6 +2128,11 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { conn.setPulledDialogId(dialogId); } + if (intentExtras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)) { + logi("dialInternal containing EXTRA_CALL_RAT_TYPE, " + + intentExtras.getString(ImsCallProfile.EXTRA_CALL_RAT_TYPE)); + } + // Pack the OEM-specific call extras. profile.mCallExtras.putBundle(ImsCallProfile.EXTRA_OEM_EXTRAS, intentExtras); @@ -1894,6 +2154,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { setVideoCallProvider(conn, imsCall); conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall); conn.setAllowHoldingVideoCall(mAllowHoldingVideoCall); + mImsCallInfoTracker.addImsCallStatus(conn); } catch (ImsException e) { loge("dialInternal : " + e); mOperationLocalLog.log("dialInternal exception: " + e); @@ -2615,6 +2876,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { + System.identityHashCode(conn)); call.onHangupLocal(); + mImsCallInfoTracker.updateImsCallStatus(conn); try { if (imsCall != null) { @@ -2815,14 +3077,26 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { conn.maybeChangeRingbackState(); maybeSetVideoCallProvider(conn, imsCall); + // IMS call profile might be changed while call state is maintained. In this case, it's + // required to notify to CallAttributesListener. + // Since call state is not changed, TelephonyRegistry will not notify to + // PreciseCallStateListener. + mPhone.notifyPreciseCallStateToNotifier(); return; } // Do not log operations that do not change the state mOperationLocalLog.log("processCallStateChange: state=" + state + " cause=" + cause + " connId=" + System.identityHashCode(conn)); - + boolean noActiveCall = false; + if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE + && mBackgroundCall.getState() != ImsPhoneCall.State.ACTIVE) { + noActiveCall = true; + } changed = conn.update(imsCall, state); + if (noActiveCall && changed && state == ImsPhoneCall.State.ACTIVE) { + sendMessage(obtainMessage(EVENT_NEW_ACTIVE_CALL_STARTED)); + } if (state == ImsPhoneCall.State.DISCONNECTED) { changed = conn.onDisconnect(cause) || changed; //detach the disconnected connections @@ -2852,6 +3126,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } if (changed) { + mImsCallInfoTracker.updateImsCallStatus(conn); if (conn.getCall() == mHandoverCall) return; updatePhoneState(); mPhone.notifyPreciseCallStateChanged(); @@ -2883,7 +3158,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { @VisibleForTesting public void addReasonCodeRemapping(Integer fromCode, String message, Integer toCode) { if (message != null) { - message = message.toLowerCase(); + message = message.toLowerCase(Locale.ROOT); } mImsReasonCodeMap.put(new ImsReasonInfoKeyPair(fromCode, message), toCode); } @@ -2904,7 +3179,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { if (reason == null) { reason = ""; } else { - reason = reason.toLowerCase(); + reason = reason.toLowerCase(Locale.ROOT); } log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = " + reason); @@ -3114,6 +3389,13 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { break; case ImsReasonInfo.CODE_SIP_BAD_REQUEST: + // Auto-missed/rejected calls can sometimes use this reason cause, but if we see it + // for outgoing calls it is just a server error. + if (callState == Call.State.DIALING || callState == Call.State.ALERTING) { + return DisconnectCause.SERVER_ERROR; + } else { + return DisconnectCause.INCOMING_AUTO_REJECTED; + } case ImsReasonInfo.CODE_REJECT_CALL_ON_OTHER_SUB: case ImsReasonInfo.CODE_REJECT_ONGOING_E911_CALL: case ImsReasonInfo.CODE_REJECT_ONGOING_CALL_SETUP: @@ -3266,6 +3548,28 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { reasonInfo.mExtraCode, reasonInfo.mExtraMessage)); + if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + ImsPhoneConnection conn = findConnection(imsCall); + // Since onCallInitiating and onCallProgressing reset mPendingMO, + // we can't depend on mPendingMO. + if ((reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL + || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED + || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) + && conn != null) { + logi("onCallStartFailed eccCategory=" + eccCategory); + if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL + || reasonInfo.getExtraCode() + == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY) { + conn.setNonDetectableEmergencyCallInfo(eccCategory); + } + conn.setImsReasonInfo(reasonInfo); + sendCallStartFailedDisconnect(imsCall, reasonInfo); + mMetrics.writeOnImsCallStartFailed(mPhone.getPhoneId(), + imsCall.getCallSession(), reasonInfo); + return; + } + } + if (mPendingMO != null) { // To initiate dialing circuit-switched call if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED @@ -3273,6 +3577,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { && isForegroundHigherPriority()) { mForegroundCall.detach(mPendingMO); removeConnection(mPendingMO); + mImsCallInfoTracker.updateImsCallStatus(mPendingMO); mPendingMO.finalize(); mPendingMO = null; // if we need to perform CSFB of call, hang up any background call @@ -3304,6 +3609,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { if (conn != null) { mForegroundCall.detach(conn); removeConnection(conn); + mImsCallInfoTracker.updateImsCallStatus(conn); } updatePhoneState(); mPhone.initiateSilentRedial(reasonInfo.getExtraCode() == @@ -3393,6 +3699,12 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY; } + // Ensure the background call is correctly marked as MERGE_COMPLETE before it is + // disconnected as part of the IMS merge conference process: + if (cause == DisconnectCause.IMS_MERGED_SUCCESSFULLY && conn != null) { + conn.onConnectionEvent(android.telecom.Connection.EVENT_MERGE_COMPLETE, null); + } + EmergencyNumberTracker emergencyNumberTracker = null; EmergencyNumber num = null; @@ -3423,6 +3735,18 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL + && DomainSelectionResolver.getInstance().isDomainSelectionSupported()) { + if (conn != null) { + int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; + if (imsCall != null && imsCall.getCallProfile() != null) { + eccCategory = imsCall.getCallProfile().getEmergencyServiceCategories(); + logi("onCallTerminated eccCategory=" + eccCategory); + } + conn.setNonDetectableEmergencyCallInfo(eccCategory); + } + processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause); + return; + } else if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL && mAutoRetryFailedWifiEmergencyCall) { Pair callInfo = new Pair<>(imsCall, reasonInfo); mPhone.getDefaultPhone().mCi.registerForOn(ImsPhoneCallTracker.this, @@ -3722,7 +4046,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { mPhone.stopOnHoldTone(conn); mOnHoldToneStarted = false; } - conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null); + conn.setRemotelyUnheld(); + mImsCallInfoTracker.updateImsCallStatus(conn, false, true); } boolean useVideoPauseWorkaround = mPhone.getContext().getResources().getBoolean( @@ -4132,6 +4457,28 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { conn.receivedRtpHeaderExtensions(rtpHeaderExtensionData); } } + + /** + * Access Network Bitrate Recommendation Query (ANBRQ), see 3GPP TS 26.114. + * This API triggers radio to send ANBRQ message to the access network to query the desired + * bitrate. + * + * @param imsCall The ImsCall the data was received on. + * @param mediaType MediaType is used to identify media stream such as audio or video. + * @param direction Direction of this packet stream (e.g. uplink or downlink). + * @param bitsPerSecond This value is the bitrate requested by the other party UE through + * RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate + * (defined in TS36.321, range: 0 ~ 8000 kbit/s). + */ + @Override + public void onCallSessionSendAnbrQuery(ImsCall imsCall, int mediaType, int direction, + int bitsPerSecond) { + if (DBG) { + log("onCallSessionSendAnbrQuery mediaType=" + mediaType + ", direction=" + + direction + ", bitPerSecond=" + bitsPerSecond); + } + handleSendAnbrQuery(mediaType, direction, bitsPerSecond); + } }; /** @@ -4269,7 +4616,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item); configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value); if (mPhone != null && mPhone.getContext() != null) { - mPhone.getContext().sendBroadcast(configChangedIntent); + mPhone.getContext().sendBroadcast( + configChangedIntent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE); } } }; @@ -4349,21 +4697,58 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { * Notify of a change to SRVCC state * @param state the new SRVCC state. */ - public void notifySrvccState(Call.SrvccState state) { + public void notifySrvccState(int state) { if (DBG) log("notifySrvccState state=" + state); - mSrvccState = state; + if (mImsManager != null) { + try { + if (state == TelephonyManager.SRVCC_STATE_HANDOVER_STARTED) { + mImsManager.notifySrvccStarted(mSrvccStartedCallback); + } else if (state == TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED) { + mImsManager.notifySrvccCompleted(); + } else if (state == TelephonyManager.SRVCC_STATE_HANDOVER_FAILED) { + mImsManager.notifySrvccFailed(); + } else if (state == TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED) { + mImsManager.notifySrvccCanceled(); + } + } catch (ImsException e) { + loge("notifySrvccState : exception " + e); + } + } + + switch(state) { + case TelephonyManager.SRVCC_STATE_HANDOVER_STARTED: + mSrvccState = Call.SrvccState.STARTED; + break; - if (mSrvccState == Call.SrvccState.COMPLETED) { - // If the dialing call had ringback, ensure it stops now, otherwise it'll keep playing - // afer the SRVCC completes. - mForegroundCall.maybeStopRingback(); + case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED: + mSrvccState = Call.SrvccState.COMPLETED; - resetState(); - transferHandoverConnections(mForegroundCall); - transferHandoverConnections(mBackgroundCall); - transferHandoverConnections(mRingingCall); - updatePhoneState(); + // If the dialing call had ringback, ensure it stops now, + // otherwise it'll keep playing afer the SRVCC completes. + mForegroundCall.maybeStopRingback(); + mForegroundCall.maybeClearRemotelyHeldStatus(); + mBackgroundCall.maybeClearRemotelyHeldStatus(); + + resetState(); + transferHandoverConnections(mForegroundCall); + transferHandoverConnections(mBackgroundCall); + transferHandoverConnections(mRingingCall); + updatePhoneState(); + mImsCallInfoTracker.notifySrvccCompleted(); + break; + + case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED: + mSrvccState = Call.SrvccState.FAILED; + break; + + case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED: + mSrvccState = Call.SrvccState.CANCELED; + break; + + default: + //ignore invalid state + return; } } @@ -4494,6 +4879,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { try { ImsFeature.Capabilities capabilities = (ImsFeature.Capabilities) args.arg1; handleFeatureCapabilityChanged(capabilities); + updateImsRegistrationInfo(); } finally { args.recycle(); } @@ -4576,6 +4962,55 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } break; } + + case EVENT_START_IMS_TRAFFIC_DONE: // fallthrough + case EVENT_CONNECTION_SETUP_FAILURE: { + ar = (AsyncResult) msg.obj; + // Not-null with EVENT_START_IMS_TRAFFIC_DONE + IImsTrafficSessionCallback callback = (IImsTrafficSessionCallback) ar.userObj; + try { + if (ar.exception == null) { + Object[] result = (Object[]) ar.result; + if (result != null && result.length > 1) { + if (callback == null) { + //EVENT_CONNECTION_SETUP_FAILURE + ImsTrafficSession session = + getImsTrafficSession((int) result[0]); + if (session != null) callback = session.mCallback; + } + if (callback == null) break; + + if (result[1] == null) callback.onReady(); + else callback.onError((ConnectionFailureInfo) result[1]); + break; + } + } + if (callback != null) { + callback.onError(new ConnectionFailureInfo(REASON_UNSPECIFIED, 0, -1)); + } + } catch (RemoteException e) { + Rlog.e(LOG_TAG, "Exception: " + e); + } + break; + } + + case EVENT_NEW_ACTIVE_CALL_STARTED: { + try { + MediaQualityStatus status = mImsManager + .queryMediaQualityStatus(MediaQualityStatus.MEDIA_SESSION_TYPE_AUDIO); + if (status != null) { + if (mPhone != null && mPhone.mDefaultPhone != null) { + if (DBG) log("notify media quality status: " + status); + mPhone.onMediaQualityStatusChanged(status); + } else { + loge("onMediaQualityStatusChanged: null phone"); + } + } + } catch (ImsException e) { + Rlog.e(LOG_TAG, "Exception in queryMediaQualityStatus: " + e); + } + break; + } } } @@ -4750,6 +5185,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { + mSupportSdpForRtpHeaderExtensions); } } + pw.println(" mSrvccTypeSupported=" + mSrvccTypeSupported); pw.println(" Event Log:"); pw.increaseIndent(); mOperationLocalLog.dump(pw); @@ -5393,7 +5829,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { mOnHoldToneStarted = true; mOnHoldToneId = System.identityHashCode(conn); } - conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null); + conn.setRemotelyHeld(); + mImsCallInfoTracker.updateImsCallStatus(conn, true, false); boolean useVideoPauseWorkaround = mPhone.getContext().getResources().getBoolean( com.android.internal.R.bool.config_useVideoPauseWorkaround); @@ -5521,4 +5958,256 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { } return false; } + + private void initializeTerminalBasedCallWaiting() { + boolean capable = false; + if (mImsManager != null) { + try { + capable = mImsManager.isCapable(CAPABILITY_TERMINAL_BASED_CALL_WAITING); + } catch (ImsException e) { + loge("initializeTerminalBasedCallWaiting : exception " + e); + } + } + logi("initializeTerminalBasedCallWaiting capable=" + capable); + mPhone.setTerminalBasedCallWaitingSupported(capable); + + if (capable) { + setTerminalBasedCallWaitingStatus(mPhone.getTerminalBasedCallWaitingState(false)); + } + } + + /** + * Notifies the change of the user setting of the terminal-based call waiting service + * to IMS service. + */ + public void setTerminalBasedCallWaitingStatus(int state) { + if (state == TERMINAL_BASED_NOT_SUPPORTED) return; + if (mImsManager != null) { + try { + log("setTerminalBasedCallWaitingStatus state=" + state); + mImsManager.setTerminalBasedCallWaitingStatus( + state == TERMINAL_BASED_ACTIVATED); + } catch (ImsException e) { + loge("setTerminalBasedCallWaitingStatus : exception " + e); + } + } + } + + /** Send the list of SrvccConnection instances to the radio */ + public void handleSrvccConnectionInfo(List profileList) { + mPhone.getDefaultPhone().mCi.setSrvccCallInfo( + convertToSrvccConnectionInfo(profileList), null); + } + + /** Converts SrvccCall to SrvccConnection. */ + @VisibleForTesting + public SrvccConnection[] convertToSrvccConnectionInfo(List profileList) { + if (mSrvccTypeSupported.isEmpty() || profileList == null || profileList.isEmpty()) { + loge("convertToSrvccConnectionInfo empty list"); + return null; + } + + List connList = new ArrayList<>(); + for (SrvccCall profile : profileList) { + if (isCallProfileSupported(profile)) { + addConnection(connList, + profile, findConnection(profile.getCallId())); + } else { + logi("convertToSrvccConnectionInfo not supported" + + " state=" + profile.getPreciseCallState()); + } + } + + logi("convertToSrvccConnectionInfo size=" + connList.size()); + if (connList.isEmpty()) return null; + return connList.toArray(new SrvccConnection[0]); + } + + /** Send the mediaType, direction, bitrate for ANBR Query to the radio */ + public void handleSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) { + if (DBG) log("handleSendAnbrQuery - mediaType=" + mediaType); + mPhone.getDefaultPhone().mCi.sendAnbrQuery(mediaType, direction, bitsPerSecond, null); + } + + + /** + * Notifies the recommended bit rate for the indicated logical channel and direction. + * + * @param mediaType MediaType is used to identify media stream such as audio or video. + * @param direction Direction of this packet stream (e.g. uplink or downlink). + * @param bitsPerSecond The recommended bit rate for the UE for a specific logical channel and + * a specific direction by NW. + */ + public void triggerNotifyAnbr(int mediaType, int direction, int bitsPerSecond) { + ImsCall activeCall = mForegroundCall.getFirstConnection().getImsCall(); + + if (activeCall != null) { + if (DBG) log("triggerNotifyAnbr - mediaType=" + mediaType); + activeCall.callSessionNotifyAnbr(mediaType, direction, bitsPerSecond); + } + } + + private boolean isCallProfileSupported(SrvccCall profile) { + if (profile == null) { + loge("isCallProfileSupported null profile"); + return false; + } + + switch (profile.getPreciseCallState()) { + case PRECISE_CALL_STATE_ACTIVE: + return mSrvccTypeSupported.contains(BASIC_SRVCC_SUPPORT); + case PRECISE_CALL_STATE_HOLDING: + return mSrvccTypeSupported.contains(MIDCALL_SRVCC_SUPPORT); + case PRECISE_CALL_STATE_DIALING: + return mSrvccTypeSupported.contains(PREALERTING_SRVCC_SUPPORT); + case PRECISE_CALL_STATE_ALERTING: + return mSrvccTypeSupported.contains(ALERTING_SRVCC_SUPPORT); + case PRECISE_CALL_STATE_INCOMING: + return mSrvccTypeSupported.contains(ALERTING_SRVCC_SUPPORT); + case PRECISE_CALL_STATE_WAITING: + return mSrvccTypeSupported.contains(ALERTING_SRVCC_SUPPORT); + case PRECISE_CALL_STATE_INCOMING_SETUP: + return mSrvccTypeSupported.contains(PREALERTING_SRVCC_SUPPORT); + default: + loge("isCallProfileSupported invalid state=" + + profile.getPreciseCallState()); + break; + } + return false; + } + + private synchronized ImsPhoneConnection findConnection(String callId) { + for (ImsPhoneConnection c : mConnections) { + ImsCall imsCall = c.getImsCall(); + if (imsCall == null) continue; + ImsCallSession session = imsCall.getCallSession(); + if (session == null) continue; + + if (TextUtils.equals(session.getCallId(), callId)) { + return c; + } + } + return null; + } + + /** + * Update the list of SrvccConnection with the given SrvccCall and ImsPhoneconnection. + * + * @param destList the list of SrvccConnection the new connection will be added + * @param profile the SrvccCall of the connection to be added + * @param c the ImsPhoneConnection of the connection to be added + */ + private void addConnection( + List destList, SrvccCall profile, ImsPhoneConnection c) { + if (destList == null) return; + if (profile == null) return; + + int preciseCallState = profile.getPreciseCallState(); + if (!isAlive(preciseCallState)) { + Rlog.i(LOG_TAG, "addConnection not alive, " + preciseCallState); + return; + } + + List participants = getConferenceParticipants(c); + if (participants != null) { + for (ConferenceParticipant cp : participants) { + if (cp.getState() == android.telecom.Connection.STATE_DISCONNECTED) { + Rlog.i(LOG_TAG, "addConnection participant is disconnected"); + continue; + } + SrvccConnection srvccConnection = new SrvccConnection(cp, preciseCallState); + Rlog.i(LOG_TAG, "addConnection " + srvccConnection); + destList.add(srvccConnection); + } + } else { + SrvccConnection srvccConnection = + new SrvccConnection(profile.getImsCallProfile(), c, preciseCallState); + Rlog.i(LOG_TAG, "addConnection " + srvccConnection); + destList.add(srvccConnection); + } + } + + private List getConferenceParticipants(ImsPhoneConnection c) { + if (!mSrvccTypeSupported.contains(MIDCALL_SRVCC_SUPPORT)) return null; + + ImsCall imsCall = c.getImsCall(); + if (imsCall == null) return null; + + List participants = imsCall.getConferenceParticipants(); + if (participants == null || participants.isEmpty()) return null; + return participants; + } + + private static boolean isAlive(int preciseCallState) { + switch (preciseCallState) { + case PRECISE_CALL_STATE_ACTIVE: return true; + case PRECISE_CALL_STATE_HOLDING: return true; + case PRECISE_CALL_STATE_DIALING: return true; + case PRECISE_CALL_STATE_ALERTING: return true; + case PRECISE_CALL_STATE_INCOMING: return true; + case PRECISE_CALL_STATE_WAITING: return true; + case PRECISE_CALL_STATE_INCOMING_SETUP: return true; + default: + } + return false; + } + + /** + * Notifies that radio triggered IMS deregistration. + * @param reason the reason why the deregistration is triggered. + */ + public void triggerImsDeregistration( + @ImsRegistrationImplBase.ImsDeregistrationReason int reason) { + if (mImsManager == null) return; + try { + mImsManager.triggerDeregistration(reason); + } catch (ImsException e) { + loge("triggerImsDeregistration: exception " + e); + } + } + + private void updateImsRegistrationInfo() { + int capabilities = 0; + + if (mMmTelCapabilities.isCapable( + MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE)) { + capabilities |= IMS_MMTEL_CAPABILITY_VOICE; + } + if (mMmTelCapabilities.isCapable( + MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO)) { + capabilities |= IMS_MMTEL_CAPABILITY_VIDEO; + } + if (mMmTelCapabilities.isCapable( + MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS)) { + capabilities |= IMS_MMTEL_CAPABILITY_SMS; + } + + mPhone.updateImsRegistrationInfo(capabilities); + } + + private void registerImsTrafficSession(int token, + @MmTelFeature.ImsTrafficType int trafficType, + @MmTelFeature.ImsTrafficDirection int trafficDirection, + @NonNull IImsTrafficSessionCallback callback) { + mImsTrafficSessions.put(Integer.valueOf(token), + new ImsTrafficSession(trafficType, trafficDirection, callback)); + } + + private void unregisterImsTrafficSession(int token) { + mImsTrafficSessions.remove(Integer.valueOf(token)); + } + + private ImsTrafficSession getImsTrafficSession(int token) { + return mImsTrafficSessions.get(Integer.valueOf(token)); + } + + private void stopAllImsTrafficTypes() { + boolean isEmpty = mImsTrafficSessions.isEmpty(); + logi("stopAllImsTrafficTypes empty=" + isEmpty); + + if (isEmpty) return; + + mImsTrafficSessions.forEachKey(1, token -> mPhone.stopImsTraffic(token, null)); + mImsTrafficSessions.clear(); + } } diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java index 14952b72fe8790cd5d22b0d81d69c0269a4ea659..71257636ef664c2bba2d43d0dba499a33e3b0781 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java @@ -510,6 +510,10 @@ class ImsPhoneCommandInterface extends BaseCommands implements CommandsInterface public void getDeviceIdentity(Message response) { } + @Override + public void getImei(Message response) { + } + @Override public void getCDMASubscription(Message response) { } @@ -603,12 +607,13 @@ class ImsPhoneCommandInterface extends BaseCommands implements CommandsInterface public void iccOpenLogicalChannel(String AID, int p2, Message response) {} @Override - public void iccCloseLogicalChannel(int channel, Message response) {} + public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) {} @Override public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, int p2, int p3, String data, - Message response) {} + boolean isEs10Command, Message response) {} + @Override public void iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2, int p3, String data, Message response) {} diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java old mode 100755 new mode 100644 index 68de4a3ec9f7847786a82c236650540a0fbb2b3b..b984d84e9e6e14d8c5abb9497d93baaa5db94519 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java @@ -152,6 +152,11 @@ public class ImsPhoneConnection extends Connection implements */ private ImsReasonInfo mImsReasonInfo; + /** + * Used to indicate that this call is held by remote party. + */ + private boolean mIsHeldByRemote = false; + //***** Event Constants private static final int EVENT_DTMF_DONE = 1; private static final int EVENT_PAUSE_DONE = 2; @@ -244,7 +249,8 @@ public class ImsPhoneConnection extends Connection implements /** This is an MO call, created when dialing */ public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct, - ImsPhoneCall parent, boolean isEmergency, boolean isWpsCall) { + ImsPhoneCall parent, boolean isEmergency, boolean isWpsCall, + ImsPhone.ImsDialArgs dialArgs) { super(PhoneConstants.PHONE_TYPE_IMS); createWakeLock(phone.getContext()); acquireWakeLock(); @@ -272,6 +278,13 @@ public class ImsPhoneConnection extends Connection implements mIsEmergency = isEmergency; if (isEmergency) { setEmergencyCallInfo(mOwner); + + if (getEmergencyNumberInfo() == null) { + // There was no emergency number info found for this call, however it is + // still marked as an emergency number. This may happen if it was a redialed + // non-detectable emergency call from IMS. + setNonDetectableEmergencyCallInfo(dialArgs.eccCategory); + } } mIsWpsCall = isWpsCall; @@ -1580,7 +1593,30 @@ public class ImsPhoneConnection extends Connection implements */ public void handleMergeComplete() { mIsMergeInProcess = false; - onConnectionEvent(android.telecom.Connection.EVENT_MERGE_COMPLETE, null); + } + + /** + * Mark the call is held by remote party and inform to the UI. + */ + public void setRemotelyHeld() { + mIsHeldByRemote = true; + onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null); + } + + /** + * Mark the call is Unheld by remote party and inform to the UI. + */ + public void setRemotelyUnheld() { + mIsHeldByRemote = false; + onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null); + } + + /** + * @return whether the remote party is holding the call. + */ + public boolean isHeldByRemote() { + Rlog.i(LOG_TAG, "isHeldByRemote=" + mIsHeldByRemote); + return mIsHeldByRemote; } public void changeToPausedState() { diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java index 738439a45d90f4defb5b7399b3fed8f65642b2b3..25fa8a201119ec6f94dcf91fd5e8f7a4478f1b94 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java @@ -58,6 +58,7 @@ import com.android.ims.ImsException; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CallForwardInfo; import com.android.internal.telephony.CallStateException; +import com.android.internal.telephony.CallWaitingController; import com.android.internal.telephony.CommandException; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.MmiCode; @@ -1102,10 +1103,25 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode { int serviceClass = siToServiceClass(mSia); if (isActivate() || isDeactivate()) { + if (serviceClass == SERVICE_CLASS_NONE + || (serviceClass & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE) { + if (mPhone.getTerminalBasedCallWaitingState(false) + != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) { + mPhone.getDefaultPhone().setCallWaiting(isActivate(), serviceClass, + obtainMessage(EVENT_SET_COMPLETE, this)); + return; + } + } mPhone.setCallWaiting(isActivate(), serviceClass, obtainMessage(EVENT_SET_COMPLETE, this)); } else if (isInterrogate()) { - mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this)); + if (mPhone.getTerminalBasedCallWaitingState(false) + != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) { + mPhone.getDefaultPhone() + .getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this)); + } else { + mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this)); + } } else { throw new RuntimeException ("Invalid or Unsupported MMI Code"); } diff --git a/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java b/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java index 115f6fe43b56ea4a163dc91ac17de27061c2cc2b..9452e2a553ccbe026c11f6416bca40cd318f8dd5 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java @@ -20,8 +20,10 @@ import android.annotation.AnyThread; import android.annotation.NonNull; import android.net.Uri; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.RegistrationManager; import android.telephony.ims.aidl.IImsRegistrationCallback; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import java.util.concurrent.Executor; @@ -40,7 +42,7 @@ public class ImsRegistrationCallbackHelper { /** * Handle the callback when IMS is registered. */ - void handleImsRegistered(int imsRadioTech); + void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes); /** * Handle the callback when IMS is registering. @@ -50,7 +52,9 @@ public class ImsRegistrationCallbackHelper { /** * Handle the callback when IMS is unregistered. */ - void handleImsUnregistered(ImsReasonInfo imsReasonInfo); + void handleImsUnregistered(ImsReasonInfo imsReasonInfo, + @RegistrationManager.SuggestedAction int suggestedAction, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech); /** * Handle the callback when the list of subscriber {@link Uri}s associated with this IMS @@ -66,9 +70,9 @@ public class ImsRegistrationCallbackHelper { private final RegistrationManager.RegistrationCallback mImsRegistrationCallback = new RegistrationManager.RegistrationCallback() { @Override - public void onRegistered(int imsRadioTech) { + public void onRegistered(@NonNull ImsRegistrationAttributes attributes) { updateRegistrationState(RegistrationManager.REGISTRATION_STATE_REGISTERED); - mImsRegistrationUpdate.handleImsRegistered(imsRadioTech); + mImsRegistrationUpdate.handleImsRegistered(attributes); } @Override @@ -79,8 +83,17 @@ public class ImsRegistrationCallbackHelper { @Override public void onUnregistered(ImsReasonInfo imsReasonInfo) { + onUnregistered(imsReasonInfo, RegistrationManager.SUGGESTED_ACTION_NONE, + ImsRegistrationImplBase.REGISTRATION_TECH_NONE); + } + + @Override + public void onUnregistered(ImsReasonInfo imsReasonInfo, + @RegistrationManager.SuggestedAction int suggestedAction, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { updateRegistrationState(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED); - mImsRegistrationUpdate.handleImsUnregistered(imsReasonInfo); + mImsRegistrationUpdate.handleImsUnregistered(imsReasonInfo, suggestedAction, + imsRadioTech); } @Override diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java index 56db0c7c5b55a821518a34f16f3518088688e23f..cfa16d0591599feaccad68f8329451f8472375d5 100644 --- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java +++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java @@ -16,10 +16,6 @@ package com.android.internal.telephony.metrics; -import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; -import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; -import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; -import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; import android.annotation.Nullable; @@ -30,18 +26,18 @@ import android.telephony.Annotation.NetworkType; import android.telephony.DataFailCause; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.ProtocolType; import android.telephony.data.DataCallResponse; import android.telephony.data.DataService; -import android.telephony.data.DataService.DeactivateDataReason; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.ServiceStateTracker; -import com.android.internal.telephony.SubscriptionController; +import com.android.internal.telephony.data.DataNetwork; import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; @@ -127,33 +123,17 @@ public class DataCallSessionStats { /** * Updates the dataCall atom when data call is deactivated. * - * @param reason Deactivate reason + * @param reason Tear down reason */ - public synchronized void setDeactivateDataCallReason(@DeactivateDataReason int reason) { + public synchronized void setDeactivateDataCallReason(@DataNetwork.TearDownReason int reason) { // there should've been another call to initiate the atom, // so this method is being called out of order -> no metric will be logged if (mDataCallSession == null) { loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated."); return; } - switch (reason) { - case DataService.REQUEST_REASON_NORMAL: - mDataCallSession.deactivateReason = - DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; - break; - case DataService.REQUEST_REASON_SHUTDOWN: - mDataCallSession.deactivateReason = - DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; - break; - case DataService.REQUEST_REASON_HANDOVER: - mDataCallSession.deactivateReason = - DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; - break; - default: - mDataCallSession.deactivateReason = - DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; - break; - } + // Skip the pre-U enum. See enum DataDeactivateReasonEnum in enums.proto + mDataCallSession.deactivateReason = reason + DataService.REQUEST_REASON_HANDOVER + 1; } /** @@ -262,9 +242,9 @@ public class DataCallSessionStats { mDataCallSession.oosAtEnd = getIsOos(); mDataCallSession.ongoing = false; // set if this data call is established for internet on the non-Dds - SubscriptionInfo subInfo = SubscriptionController.getInstance() + SubscriptionInfo subInfo = SubscriptionManagerService.getInstance() .getSubscriptionInfo(mPhone.getSubId()); - if (mPhone.getSubId() != SubscriptionController.getInstance().getDefaultDataSubId() + if (mPhone.getSubId() != SubscriptionManager.getDefaultDataSubscriptionId() && ((mDataCallSession.apnTypeBitmask & ApnSetting.TYPE_DEFAULT) == ApnSetting.TYPE_DEFAULT) && subInfo != null && !subInfo.isOpportunistic()) { @@ -326,7 +306,7 @@ public class DataCallSessionStats { proto.setupFailed = false; proto.failureCause = DataFailCause.NONE; proto.suggestedRetryMillis = 0; - proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; + proto.deactivateReason = DataNetwork.TEAR_DOWN_REASON_NONE; proto.durationMinutes = 0; proto.ongoing = true; proto.handoverFailureCauses = new int[0]; @@ -343,13 +323,9 @@ public class DataCallSessionStats { } private boolean getIsOpportunistic() { - if (mPhone.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(mPhone.getSubId()); - return subInfo != null && subInfo.isOpportunistic(); - } - SubscriptionController subController = SubscriptionController.getInstance(); - return subController != null && subController.isOpportunistic(mPhone.getSubId()); + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(mPhone.getSubId()); + return subInfo != null && subInfo.isOpportunistic(); } private boolean getIsOos() { diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java index 9ad775f30ec0e3fcd7d907dc326f74534531917c..2f22196f45e3feaa968174a716b9e8a224393f62 100644 --- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java +++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java @@ -27,7 +27,6 @@ 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.SubscriptionController; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.data.DataStallRecoveryManager; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; @@ -147,12 +146,8 @@ public class DataStallRecoveryStats { } private static boolean getIsOpportunistic(Phone phone) { - if (phone.isSubscriptionManagerServiceEnabled()) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(phone.getSubId()); - return subInfo != null && subInfo.isOpportunistic(); - } - SubscriptionController subController = SubscriptionController.getInstance(); - return subController != null && subController.isOpportunistic(phone.getSubId()); + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(phone.getSubId()); + return subInfo != null && subInfo.isOpportunistic(); } } diff --git a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..29729c80921381820e4dc0bb162f0500de690e8b --- /dev/null +++ b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.metrics; + +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED; +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_FLIPPED; +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_HALF_OPENED; +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_OPENED; +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN; + +import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; + +import com.android.internal.telephony.Phone; + +/** Device state information like the fold state. */ +public class DeviceStateHelper { + private int mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN; + + public DeviceStateHelper(Context context) { + HandlerThread mHandlerThread = new HandlerThread("DeviceStateHelperThread"); + mHandlerThread.start(); + context.getSystemService(DeviceStateManager.class) + .registerCallback( + new HandlerExecutor(new Handler(mHandlerThread.getLooper())), + state -> { + updateFoldState(state); + }); + } + + private void updateFoldState(int posture) { + switch (posture) { + case 0: + mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED; + break; + case 1: + mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_HALF_OPENED; + break; + case 2: + mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_OPENED; + break; + case 4: + mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_FLIPPED; + break; + default: + mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN; + } + updateServiceStateStats(); + } + + private void updateServiceStateStats() { + for (Phone phone : MetricsCollector.getPhonesIfAny()) { + phone.getServiceStateTracker().getServiceStateStats().onFoldStateChanged(mFoldState); + } + } + + public int getFoldState() { + return mFoldState; + } +} diff --git a/src/java/com/android/internal/telephony/metrics/DeviceTelephonyPropertiesStats.java b/src/java/com/android/internal/telephony/metrics/DeviceTelephonyPropertiesStats.java new file mode 100644 index 0000000000000000000000000000000000000000..51fe20c0dea51fdd35182617c896f465bc93e64b --- /dev/null +++ b/src/java/com/android/internal/telephony/metrics/DeviceTelephonyPropertiesStats.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.metrics; + +import com.android.internal.telephony.PhoneFactory; + +/** Metrics for the telephony related properties on the device. */ +public class DeviceTelephonyPropertiesStats { + private static final String TAG = DeviceTelephonyPropertiesStats.class.getSimpleName(); + + /** + * Record whenever the auto data switch feature is toggled. + */ + public static void recordAutoDataSwitchFeatureToggle() { + PersistAtomsStorage storage = PhoneFactory.getMetricsCollector().getAtomsStorage(); + storage.recordToggledAutoDataSwitch(); + } +} diff --git a/src/java/com/android/internal/telephony/metrics/EmergencyNumberStats.java b/src/java/com/android/internal/telephony/metrics/EmergencyNumberStats.java new file mode 100644 index 0000000000000000000000000000000000000000..2867b46206449da0d38dee2097f5473884ebc841 --- /dev/null +++ b/src/java/com/android/internal/telephony/metrics/EmergencyNumberStats.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.metrics; + +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_EMERGENCY; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_NORMAL; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_UNKNOWN; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AIEC; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AMBULANCE; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MIEC; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_POLICE; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DATABASE; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DEFAULT; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_SIM; + +import android.telephony.emergency.EmergencyNumber; +import android.util.SparseIntArray; + +import com.android.internal.telephony.nano.PersistAtomsProto; + +import java.util.ArrayList; +import java.util.List; + +/** + * EmergencyStats logs the atoms for consolidated emergency number list in framework. It also logs + * the details of a dialed emergency number. To avoid repeated information this class stores the + * emergency numbers list in map and verifies the information for duplicacy before logging it. Note: + * This locally stored information will erase on process restart scenarios (like reboot, crash, + * etc.). + */ +public class EmergencyNumberStats { + + private static final String TAG = EmergencyNumberStats.class.getSimpleName(); + private static final SparseIntArray sRoutesMap; + private static final SparseIntArray sServiceCategoriesMap; + private static final SparseIntArray sSourcesMap; + private static EmergencyNumberStats sInstance; + + static { + sRoutesMap = new SparseIntArray() { + { + put(EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY, + EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_EMERGENCY); + put(EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL, + EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_NORMAL); + put(EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN, + EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_UNKNOWN); + } + }; + + sServiceCategoriesMap = new SparseIntArray() { + { + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED); + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_POLICE); + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AMBULANCE); + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE); + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD); + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE); + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MIEC); + put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC, + EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AIEC); + } + }; + + sSourcesMap = new SparseIntArray() { + { + put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING); + put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_SIM); + put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DATABASE); + put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG, + EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG); + put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT, + EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DEFAULT); + } + }; + } + + private EmergencyNumberStats() { + } + + /** Static method to provide singleton instance for EmergencyNumberStats. */ + public static EmergencyNumberStats getInstance() { + if (sInstance == null) { + sInstance = new EmergencyNumberStats(); + } + return sInstance; + } + + /** + * It converts the {@link android.telephony.emergency.EmergencyNumber} to + * {@link PersistAtomsProto.EmergencyNumber} for + * logging the EmergencyNumber atoms with pulled event. + * + * @param emergencyNumberList android.telephony.EmergencyNumber list + * @param assetVersion assert version + * @param otaVersion ota version + * @param isDbRoutingIgnored flag that defines if routing is ignored through database. + */ + public PersistAtomsProto.EmergencyNumbersInfo[] convertEmergencyNumbersListToProto( + List emergencyNumberList, int assetVersion, int otaVersion, + boolean isDbRoutingIgnored) { + List numberProtoList = new ArrayList<>(); + for (EmergencyNumber number : emergencyNumberList) { + numberProtoList.add(convertEmergencyNumberToProto(number, assetVersion, otaVersion, + isDbRoutingIgnored)); + } + return numberProtoList.toArray(new PersistAtomsProto.EmergencyNumbersInfo[0]); + } + + private PersistAtomsProto.EmergencyNumbersInfo convertEmergencyNumberToProto( + EmergencyNumber number, int assetVer, int otaVer, boolean isDbRoutingIgnored) { + String dialNumber = number.getNumber(); + PersistAtomsProto.EmergencyNumbersInfo emergencyNumber = + new PersistAtomsProto.EmergencyNumbersInfo(); + emergencyNumber.isDbVersionIgnored = isDbRoutingIgnored; + emergencyNumber.assetVersion = assetVer; + emergencyNumber.otaVersion = otaVer; + emergencyNumber.number = dialNumber; + emergencyNumber.countryIso = number.getCountryIso(); + emergencyNumber.mnc = number.getMnc(); + emergencyNumber.route = sRoutesMap.get(number.getEmergencyCallRouting()); + emergencyNumber.urns = number.getEmergencyUrns().toArray(new String[0]); + emergencyNumber.serviceCategories = getMappedServiceCategories( + number.getEmergencyServiceCategories()); + emergencyNumber.sources = getMappedSources(number.getEmergencyNumberSources()); + return emergencyNumber; + } + + private int[] getMappedServiceCategories(List serviceCategories) { + if (serviceCategories == null || serviceCategories.isEmpty()) { + return null; + } + return serviceCategories.stream().map(sServiceCategoriesMap::get).mapToInt( + Integer::intValue).toArray(); + } + + private int[] getMappedSources(List sources) { + if (sources == null || sources.isEmpty()) { + return null; + } + return sources.stream().map(sSourcesMap::get).mapToInt(Integer::intValue).toArray(); + } +} diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java index 412930f2d6076f812a9f8a7135937eecf753ecd8..5e009879112f08336860b4c59d8a96294f754936 100644 --- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java +++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java @@ -16,15 +16,12 @@ package com.android.internal.telephony.metrics; -import static android.text.format.DateUtils.HOUR_IN_MILLIS; -import static android.text.format.DateUtils.MINUTE_IN_MILLIS; -import static android.text.format.DateUtils.SECOND_IN_MILLIS; - import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_VERSION; import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH; import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION; import static com.android.internal.telephony.TelephonyStatsLog.DEVICE_TELEPHONY_PROPERTIES; +import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO; import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT; import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_EVENT; import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_LISTENER_EVENT; @@ -33,11 +30,18 @@ import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_ import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_STATS; import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_TERMINATION; import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS; +import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SHORT_CODE_SMS; import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS; import static com.android.internal.telephony.TelephonyStatsLog.PRESENCE_NOTIFY_EVENT; import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS; import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS; +import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONTROLLER; +import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_INCOMING_DATAGRAM; +import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_OUTGOING_DATAGRAM; +import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_PROVISION; +import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_SESSION; +import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_SOS_MESSAGE_RECOMMENDER; import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE; import static com.android.internal.telephony.TelephonyStatsLog.SIP_DELEGATE_STATS; import static com.android.internal.telephony.TelephonyStatsLog.SIP_MESSAGE_RESPONSE; @@ -52,16 +56,20 @@ import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSIO import android.app.StatsManager; import android.content.Context; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.util.StatsEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyStatsLog; +import com.android.internal.telephony.emergency.EmergencyNumberTracker; import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; +import com.android.internal.telephony.nano.PersistAtomsProto.EmergencyNumbersInfo; import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent; import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent; import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent; @@ -71,10 +79,17 @@ import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStat import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination; import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms; import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2; +import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms; import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms; import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent; import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats; import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender; import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats; import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse; import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats; @@ -85,6 +100,7 @@ import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession; import com.android.internal.util.ConcurrentUtils; import com.android.telephony.Rlog; +import java.time.Duration; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -104,6 +120,10 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { /** Disables various restrictions to ease debugging during development. */ private static final boolean DBG = false; // STOPSHIP if true + private static final long MILLIS_PER_HOUR = Duration.ofHours(1).toMillis(); + private static final long MILLIS_PER_MINUTE = Duration.ofMinutes(1).toMillis(); + private static final long MILLIS_PER_SECOND = Duration.ofSeconds(1).toMillis(); + /** * Sets atom pull cool down to 23 hours to help enforcing privacy requirement. * @@ -111,7 +131,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { * that occur once a day. */ private static final long MIN_COOLDOWN_MILLIS = - DBG ? 10L * SECOND_IN_MILLIS : 23L * HOUR_IN_MILLIS; + DBG ? 10L * MILLIS_PER_SECOND : 23L * MILLIS_PER_HOUR; /** * Buckets with less than these many calls will be dropped. @@ -122,25 +142,28 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { /** Bucket size in milliseconds to round call durations into. */ private static final long DURATION_BUCKET_MILLIS = - DBG ? 2L * SECOND_IN_MILLIS : 5L * MINUTE_IN_MILLIS; + DBG ? 2L * MILLIS_PER_SECOND : 5L * MILLIS_PER_MINUTE; private final PersistAtomsStorage mStorage; + private final DeviceStateHelper mDeviceStateHelper; private final StatsManager mStatsManager; private final AirplaneModeStats mAirplaneModeStats; private final Set mOngoingDataCallStats = ConcurrentHashMap.newKeySet(); private static final Random sRandom = new Random(); public MetricsCollector(Context context) { - this(context, new PersistAtomsStorage(context)); + this(context, new PersistAtomsStorage(context), new DeviceStateHelper(context)); } /** Allows dependency injection. Used during unit tests. */ @VisibleForTesting - public MetricsCollector(Context context, - PersistAtomsStorage storage) { + public MetricsCollector( + Context context, PersistAtomsStorage storage, DeviceStateHelper deviceStateHelper) { mStorage = storage; + mDeviceStateHelper = deviceStateHelper; mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER); if (mStatsManager != null) { + // Most (but not all) of these are subject to cooldown specified by MIN_COOLDOWN_MILLIS. registerAtom(CELLULAR_DATA_SERVICE_SWITCH); registerAtom(CELLULAR_SERVICE_STATE); registerAtom(SIM_SLOT_STATE); @@ -169,6 +192,14 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { registerAtom(PRESENCE_NOTIFY_EVENT); registerAtom(GBA_EVENT); registerAtom(PER_SIM_STATUS); + registerAtom(OUTGOING_SHORT_CODE_SMS); + registerAtom(SATELLITE_CONTROLLER); + registerAtom(SATELLITE_SESSION); + registerAtom(SATELLITE_INCOMING_DATAGRAM); + registerAtom(SATELLITE_OUTGOING_DATAGRAM); + registerAtom(SATELLITE_PROVISION); + registerAtom(SATELLITE_SOS_MESSAGE_RECOMMENDER); + registerAtom(EMERGENCY_NUMBERS_INFO); Rlog.d(TAG, "registered"); } else { Rlog.e(TAG, "could not get StatsManager, atoms not registered"); @@ -243,6 +274,22 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { return pullGbaEvent(data); case PER_SIM_STATUS: return pullPerSimStatus(data); + case OUTGOING_SHORT_CODE_SMS: + return pullOutgoingShortCodeSms(data); + case SATELLITE_CONTROLLER: + return pullSatelliteController(data); + case SATELLITE_SESSION: + return pullSatelliteSession(data); + case SATELLITE_INCOMING_DATAGRAM: + return pullSatelliteIncomingDatagram(data); + case SATELLITE_OUTGOING_DATAGRAM: + return pullSatelliteOutgoingDatagram(data); + case SATELLITE_PROVISION: + return pullSatelliteProvision(data); + case SATELLITE_SOS_MESSAGE_RECOMMENDER: + return pullSatelliteSosMessageRecommender(data); + case EMERGENCY_NUMBERS_INFO: + return pullEmergencyNumbersInfo(data); default: Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag)); return StatsManager.PULL_SKIP; @@ -254,6 +301,23 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { return mStorage; } + /** Returns the {@link DeviceStateHelper}. */ + public DeviceStateHelper getDeviceStateHelper() { + return mDeviceStateHelper; + } + + /** Updates duration segments and calls {@link PersistAtomsStorage#flushAtoms()}. */ + public void flushAtomsStorage() { + concludeAll(); + mStorage.flushAtoms(); + } + + /** Updates duration segments and calls {@link PersistAtomsStorage#clearAtoms()}. */ + public void clearAtomsStorage() { + concludeAll(); + mStorage.clearAtoms(); + } + /** * Registers a {@link DataCallSessionStats} which will be pinged for on-going data calls when * data call atoms are pulled. @@ -267,6 +331,44 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { mOngoingDataCallStats.remove(call); } + private void concludeDataCallSessionStats() { + for (DataCallSessionStats stats : mOngoingDataCallStats) { + stats.conclude(); + } + } + + private void concludeImsStats() { + for (Phone phone : getPhonesIfAny()) { + ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); + if (imsPhone != null) { + imsPhone.getImsStats().conclude(); + } + } + } + + private void concludeServiceStateStats() { + for (Phone phone : getPhonesIfAny()) { + phone.getServiceStateTracker().getServiceStateStats().conclude(); + } + } + + private void concludeRcsStats() { + RcsStats rcsStats = RcsStats.getInstance(); + if (rcsStats != null) { + rcsStats.concludeSipTransportFeatureTagsStat(); + rcsStats.onFlushIncompleteRcsAcsProvisioningStats(); + rcsStats.onFlushIncompleteImsRegistrationServiceDescStats(); + rcsStats.onFlushIncompleteImsRegistrationFeatureTagStats(); + } + } + + private void concludeAll() { + concludeDataCallSessionStats(); + concludeImsStats(); + concludeServiceStateStats(); + concludeRcsStats(); + } + private static int pullSimSlotState(List data) { SimSlotState state; try { @@ -374,10 +476,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { private int pullDataCallSession(List data) { // Include ongoing data call segments - for (DataCallSessionStats stats : mOngoingDataCallStats) { - stats.conclude(); - } - + concludeDataCallSessionStats(); DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS); if (dataCallSessions != null) { Arrays.stream(dataCallSessions) @@ -405,10 +504,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { private int pullCellularServiceState(List data) { // Include the latest durations - for (Phone phone : getPhonesIfAny()) { - phone.getServiceStateTracker().getServiceStateStats().conclude(); - } - + concludeServiceStateStats(); CellularServiceState[] persistAtoms = mStorage.getCellularServiceStates(MIN_COOLDOWN_MILLIS); if (persistAtoms != null) { @@ -424,13 +520,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { private int pullImsRegistrationStats(List data) { // Include the latest durations - for (Phone phone : getPhonesIfAny()) { - ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); - if (imsPhone != null) { - imsPhone.getImsStats().conclude(); - } - } - + concludeImsStats(); ImsRegistrationStats[] persistAtoms = mStorage.getImsRegistrationStats(MIN_COOLDOWN_MILLIS); if (persistAtoms != null) { // list is already shuffled when instances were inserted @@ -469,13 +559,22 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { } } - private static int pullDeviceTelephonyProperties(List data) { + private int pullDeviceTelephonyProperties(List data) { Phone[] phones = getPhonesIfAny(); if (phones.length == 0) { return StatsManager.PULL_SKIP; } - - data.add(TelephonyStatsLog.buildStatsEvent(DEVICE_TELEPHONY_PROPERTIES, true)); + boolean isAutoDataSwitchOn = Arrays.stream(phones) + .anyMatch(phone -> + phone.getSubId() != SubscriptionManager.getDefaultDataSubscriptionId() + && phone.getDataSettingsManager().isMobileDataPolicyEnabled( + TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)); + boolean hasDedicatedManagedProfileSub = Arrays.stream(phones) + .anyMatch(Phone::isManagedProfile); + + data.add(TelephonyStatsLog.buildStatsEvent(DEVICE_TELEPHONY_PROPERTIES, true, + isAutoDataSwitchOn, mStorage.getAutoDataSwitchToggleCount(), + hasDedicatedManagedProfileSub)); return StatsManager.PULL_SUCCESS; } @@ -677,13 +776,122 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { perSimStatus.pin1Enabled, // isPin1Enabled perSimStatus.minimumVoltageClass, // simVoltageClass perSimStatus.userModifiedApnTypes, // userModifiedApnTypeBitmask - perSimStatus.unmeteredNetworks); // unmeteredNetworks + perSimStatus.unmeteredNetworks, // unmeteredNetworks + perSimStatus.vonrEnabled); // vonrEnabled data.add(statsEvent); result = StatsManager.PULL_SUCCESS; } return result; } + private int pullOutgoingShortCodeSms(List data) { + OutgoingShortCodeSms[] outgoingShortCodeSmsList = mStorage + .getOutgoingShortCodeSms(MIN_COOLDOWN_MILLIS); + if (outgoingShortCodeSmsList != null) { + // Outgoing short code SMS list is already shuffled when SMS were inserted + Arrays.stream(outgoingShortCodeSmsList).forEach(sms -> data.add(buildStatsEvent(sms))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "OUTGOING_SHORT_CODE_SMS pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + + private int pullSatelliteController(List data) { + SatelliteController[] controllerAtoms = + mStorage.getSatelliteControllerStats(MIN_COOLDOWN_MILLIS); + if (controllerAtoms != null) { + Arrays.stream(controllerAtoms) + .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "SATELLITE_CONTROLLER pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + + private int pullSatelliteSession(List data) { + SatelliteSession[] sessionAtoms = + mStorage.getSatelliteSessionStats(MIN_COOLDOWN_MILLIS); + if (sessionAtoms != null) { + Arrays.stream(sessionAtoms) + .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "SATELLITE_SESSION pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + + private int pullSatelliteIncomingDatagram(List data) { + SatelliteIncomingDatagram[] incomingDatagramAtoms = + mStorage.getSatelliteIncomingDatagramStats(MIN_COOLDOWN_MILLIS); + if (incomingDatagramAtoms != null) { + Arrays.stream(incomingDatagramAtoms) + .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "SATELLITE_INCOMING_DATAGRAM pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + + + private int pullSatelliteOutgoingDatagram(List data) { + SatelliteOutgoingDatagram[] outgoingDatagramAtoms = + mStorage.getSatelliteOutgoingDatagramStats(MIN_COOLDOWN_MILLIS); + if (outgoingDatagramAtoms != null) { + Arrays.stream(outgoingDatagramAtoms) + .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "SATELLITE_OUTGOING_DATAGRAM pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + + + private int pullSatelliteProvision(List data) { + SatelliteProvision[] provisionAtoms = + mStorage.getSatelliteProvisionStats(MIN_COOLDOWN_MILLIS); + if (provisionAtoms != null) { + Arrays.stream(provisionAtoms) + .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "SATELLITE_PROVISION pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + + private int pullSatelliteSosMessageRecommender(List data) { + SatelliteSosMessageRecommender[] sosMessageRecommenderAtoms = + mStorage.getSatelliteSosMessageRecommenderStats(MIN_COOLDOWN_MILLIS); + if (sosMessageRecommenderAtoms != null) { + Arrays.stream(sosMessageRecommenderAtoms) + .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "SATELLITE_SOS_MESSAGE_RECOMMENDER pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + + private int pullEmergencyNumbersInfo(List data) { + boolean isDataLogged = false; + for (Phone phone : getPhonesIfAny()) { + if (phone != null) { + EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker(); + if (tracker != null) { + EmergencyNumbersInfo[] numList = tracker.getEmergencyNumbersProtoArray(); + Arrays.stream(numList).forEach(number -> data.add(buildStatsEvent(number))); + isDataLogged = true; + } + } + } + return isDataLogged ? StatsManager.PULL_SUCCESS : StatsManager.PULL_SKIP; + } + /** Registers a pulled atom ID {@code atomId}. */ private void registerAtom(int atomId) { mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null, @@ -712,8 +920,10 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { state.simSlotIndex, state.isMultiSim, state.carrierId, - (int) (round(state.totalTimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), - state.isEmergencyOnly); + roundAndConvertMillisToSeconds(state.totalTimeMillis), + state.isEmergencyOnly, + state.isInternetPdnUp, + state.foldState); } private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) { @@ -721,7 +931,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { VOICE_CALL_RAT_USAGE, usage.carrierId, usage.rat, - round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS, + roundAndConvertMillisToSeconds(usage.totalDurationMillis), usage.callCount); } @@ -764,7 +974,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { session.ratAtConnected, session.isMultiparty, session.callDuration, - session.lastKnownRat); + session.lastKnownRat, + session.foldState); } private static StatsEvent buildStatsEvent(IncomingSms sms) { @@ -784,7 +995,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { sms.isEsim, sms.carrierId, sms.messageId, - sms.count); + sms.count, + sms.isManagedProfile); } private static StatsEvent buildStatsEvent(OutgoingSms sms) { @@ -804,7 +1016,10 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { sms.messageId, sms.retryId, sms.intervalMillis, - sms.count); + sms.count, + sms.sendErrorCode, + sms.networkErrorCode, + sms.isManagedProfile); } private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) { @@ -826,7 +1041,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { dataCallSession.failureCause, dataCallSession.suggestedRetryMillis, dataCallSession.deactivateReason, - round(dataCallSession.durationMinutes, DURATION_BUCKET_MILLIS / MINUTE_IN_MILLIS), + roundAndConvertMillisToMinutes( + dataCallSession.durationMinutes * MILLIS_PER_MINUTE), dataCallSession.ongoing, dataCallSession.bandAtEnd, dataCallSession.handoverFailureCauses, @@ -840,19 +1056,15 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.carrierId, stats.simSlotIndex, stats.rat, - (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), - (int) (round(stats.voiceCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), - (int) - (round(stats.voiceAvailableMillis, DURATION_BUCKET_MILLIS) - / SECOND_IN_MILLIS), - (int) (round(stats.smsCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), - (int) (round(stats.smsAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), - (int) (round(stats.videoCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), - (int) - (round(stats.videoAvailableMillis, DURATION_BUCKET_MILLIS) - / SECOND_IN_MILLIS), - (int) (round(stats.utCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), - (int) (round(stats.utAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS)); + roundAndConvertMillisToSeconds(stats.registeredMillis), + roundAndConvertMillisToSeconds(stats.voiceCapableMillis), + roundAndConvertMillisToSeconds(stats.voiceAvailableMillis), + roundAndConvertMillisToSeconds(stats.smsCapableMillis), + roundAndConvertMillisToSeconds(stats.smsAvailableMillis), + roundAndConvertMillisToSeconds(stats.videoCapableMillis), + roundAndConvertMillisToSeconds(stats.videoAvailableMillis), + roundAndConvertMillisToSeconds(stats.utCapableMillis), + roundAndConvertMillisToSeconds(stats.utAvailableMillis)); } private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) { @@ -883,7 +1095,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.slotId, stats.featureTagName, stats.registrationTech, - (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS)); + roundAndConvertMillisToSeconds(stats.registeredMillis)); } private static StatsEvent buildStatsEvent(RcsClientProvisioningStats stats) { @@ -904,7 +1116,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.responseType, stats.isSingleRegistrationEnabled, stats.count, - (int) (round(stats.stateTimerMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS)); + roundAndConvertMillisToSeconds(stats.stateTimerMillis)); } private static StatsEvent buildStatsEvent(SipDelegateStats stats) { @@ -913,7 +1125,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.dimension, stats.carrierId, stats.slotId, - (int) (round(stats.uptimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS), + roundAndConvertMillisToSeconds(stats.uptimeMillis), stats.destroyReason); } @@ -925,7 +1137,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.featureTagName, stats.sipTransportDeniedReason, stats.sipTransportDeregisteredReason, - (int) (round(stats.associatedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS)); + roundAndConvertMillisToSeconds(stats.associatedMillis)); } private static StatsEvent buildStatsEvent(SipMessageResponse stats) { @@ -985,7 +1197,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.serviceIdName, stats.serviceIdVersion, stats.registrationTech, - (int) (round(stats.publishedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS)); + roundAndConvertMillisToSeconds(stats.publishedMillis)); } private static StatsEvent buildStatsEvent(UceEventStats stats) { @@ -1023,8 +1235,97 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.count); } + private static StatsEvent buildStatsEvent(OutgoingShortCodeSms shortCodeSms) { + return TelephonyStatsLog.buildStatsEvent( + OUTGOING_SHORT_CODE_SMS, + shortCodeSms.category, + shortCodeSms.xmlVersion, + shortCodeSms.shortCodeSmsCount); + } + + private static StatsEvent buildStatsEvent(SatelliteController satelliteController) { + return TelephonyStatsLog.buildStatsEvent( + SATELLITE_CONTROLLER, + satelliteController.countOfSatelliteServiceEnablementsSuccess, + satelliteController.countOfSatelliteServiceEnablementsFail, + satelliteController.countOfOutgoingDatagramSuccess, + satelliteController.countOfOutgoingDatagramFail, + satelliteController.countOfIncomingDatagramSuccess, + satelliteController.countOfIncomingDatagramFail, + satelliteController.countOfDatagramTypeSosSmsSuccess, + satelliteController.countOfDatagramTypeSosSmsFail, + satelliteController.countOfDatagramTypeLocationSharingSuccess, + satelliteController.countOfDatagramTypeLocationSharingFail, + satelliteController.countOfProvisionSuccess, + satelliteController.countOfProvisionFail, + satelliteController.countOfDeprovisionSuccess, + satelliteController.countOfDeprovisionFail, + satelliteController.totalServiceUptimeSec, + satelliteController.totalBatteryConsumptionPercent, + satelliteController.totalBatteryChargedTimeSec); + } + + private static StatsEvent buildStatsEvent(SatelliteSession satelliteSession) { + return TelephonyStatsLog.buildStatsEvent( + SATELLITE_SESSION, + satelliteSession.satelliteServiceInitializationResult, + satelliteSession.satelliteTechnology, + satelliteSession.count); + } + + private static StatsEvent buildStatsEvent(SatelliteIncomingDatagram stats) { + return TelephonyStatsLog.buildStatsEvent( + SATELLITE_INCOMING_DATAGRAM, + stats.resultCode, + stats.datagramSizeBytes, + stats.datagramTransferTimeMillis); + } + + private static StatsEvent buildStatsEvent(SatelliteOutgoingDatagram stats) { + return TelephonyStatsLog.buildStatsEvent( + SATELLITE_OUTGOING_DATAGRAM, + stats.datagramType, + stats.resultCode, + stats.datagramSizeBytes, + stats.datagramTransferTimeMillis); + } + + private static StatsEvent buildStatsEvent(SatelliteProvision stats) { + return TelephonyStatsLog.buildStatsEvent( + SATELLITE_PROVISION, + stats.resultCode, + stats.provisioningTimeSec, + stats.isProvisionRequest, + stats.isCanceled); + } + + private static StatsEvent buildStatsEvent(SatelliteSosMessageRecommender stats) { + return TelephonyStatsLog.buildStatsEvent( + SATELLITE_SOS_MESSAGE_RECOMMENDER, + stats.isDisplaySosMessageSent, + stats.countOfTimerStarted, + stats.isImsRegistered, + stats.cellularServiceState, + stats.count); + } + + private static StatsEvent buildStatsEvent(EmergencyNumbersInfo emergencyNumber) { + return TelephonyStatsLog.buildStatsEvent( + EMERGENCY_NUMBERS_INFO, + emergencyNumber.isDbVersionIgnored, + emergencyNumber.assetVersion, + emergencyNumber.otaVersion, + emergencyNumber.number, + emergencyNumber.countryIso, + emergencyNumber.mnc, + emergencyNumber.route, + emergencyNumber.urns, + emergencyNumber.serviceCategories, + emergencyNumber.sources); + } + /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */ - private static Phone[] getPhonesIfAny() { + static Phone[] getPhonesIfAny() { try { return PhoneFactory.getPhones(); } catch (IllegalStateException e) { @@ -1033,8 +1334,21 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { } } - /** Returns the value rounded to the bucket. */ - private static long round(long value, long bucket) { - return bucket == 0 ? value : ((value + bucket / 2) / bucket) * bucket; + /** + * Rounds the duration and converts it from milliseconds to seconds. + */ + private static int roundAndConvertMillisToSeconds(long valueMillis) { + long roundedValueMillis = Math.round((double) valueMillis / DURATION_BUCKET_MILLIS) + * DURATION_BUCKET_MILLIS; + return (int) (roundedValueMillis / MILLIS_PER_SECOND); + } + + /** + * Rounds the duration and converts it from milliseconds to minutes. + */ + private static int roundAndConvertMillisToMinutes(long valueMillis) { + long roundedValueMillis = Math.round((double) valueMillis / DURATION_BUCKET_MILLIS) + * DURATION_BUCKET_MILLIS; + return (int) (roundedValueMillis / MILLIS_PER_MINUTE); } } diff --git a/src/java/com/android/internal/telephony/metrics/PerSimStatus.java b/src/java/com/android/internal/telephony/metrics/PerSimStatus.java index 0b5581515fed7e6d8d736f05d0b716f3966bfadf..bc1edc37692c169b2179fbede8be1cf23189c7f1 100644 --- a/src/java/com/android/internal/telephony/metrics/PerSimStatus.java +++ b/src/java/com/android/internal/telephony/metrics/PerSimStatus.java @@ -32,7 +32,6 @@ import android.annotation.Nullable; import android.database.Cursor; import android.net.Uri; import android.provider.Telephony; -import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; @@ -43,14 +42,11 @@ import android.text.TextUtils; import com.android.internal.telephony.IccCard; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.UiccController; import com.android.internal.telephony.uicc.UiccSlot; -import java.util.Optional; - /** Stores the per SIM status. */ public class PerSimStatus { private static final long BITMASK_2G = @@ -76,6 +72,7 @@ public class PerSimStatus { public final int minimumVoltageClass; public final int userModifiedApnTypes; public final long unmeteredNetworks; + public final boolean vonrEnabled; /** Returns the current sim status of the given {@link Phone}. */ @Nullable @@ -107,7 +104,8 @@ public class PerSimStatus { iccCard == null ? false : iccCard.getIccLockEnabled(), getMinimumVoltageClass(phone), getUserModifiedApnTypes(phone), - persistAtomsStorage.getUnmeteredNetworks(phone.getPhoneId(), carrierId)); + persistAtomsStorage.getUnmeteredNetworks(phone.getPhoneId(), carrierId), + isVonrEnabled(phone)); } private PerSimStatus( @@ -126,7 +124,8 @@ public class PerSimStatus { boolean pin1Enabled, int minimumVoltageClass, int userModifiedApnTypes, - long unmeteredNetworks) { + long unmeteredNetworks, + boolean vonrEnabled) { this.carrierId = carrierId; this.phoneNumberSourceUicc = phoneNumberSourceUicc; this.phoneNumberSourceCarrier = phoneNumberSourceCarrier; @@ -143,6 +142,7 @@ public class PerSimStatus { this.minimumVoltageClass = minimumVoltageClass; this.userModifiedApnTypes = userModifiedApnTypes; this.unmeteredNetworks = unmeteredNetworks; + this.vonrEnabled = vonrEnabled; } @Nullable @@ -175,40 +175,21 @@ public class PerSimStatus { String countryIso = ""; String[] numbersFromAllSources; - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - if (SubscriptionManagerService.getInstance() == null) return null; - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(phone.getSubId()); - if (subInfo != null) { - countryIso = subInfo.getCountryIso(); - } - numbersFromAllSources = new String[]{ - SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(), - SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, null, null), - SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(), - SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, null, null), - SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(), - SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, null, null) - }; - } else { - SubscriptionController subscriptionController = SubscriptionController.getInstance(); - if (subscriptionController == null) { - return null; - } - int subId = phone.getSubId(); - countryIso = Optional.ofNullable(subscriptionController.getSubscriptionInfo(subId)) - .map(SubscriptionInfo::getCountryIso) - .orElse(""); - // numbersFromAllSources[] - phone numbers from each sources: - numbersFromAllSources = new String[]{ - subscriptionController.getPhoneNumber(subId, - SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, null, null), // 0 - subscriptionController.getPhoneNumber(subId, - SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, null, null), // 1 - subscriptionController.getPhoneNumber(subId, - SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, null, null), // 2 - }; + if (SubscriptionManagerService.getInstance() == null) return null; + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(phone.getSubId()); + if (subInfo != null) { + countryIso = subInfo.getCountryIso(); } + numbersFromAllSources = new String[]{ + SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(), + SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, null, null), + SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(), + SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, null, null), + SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(), + SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, null, null) + }; + int[] numberIds = new int[numbersFromAllSources.length]; // default value 0 for (int i = 0, idForNextUniqueNumber = 1; i < numberIds.length; i++) { if (TextUtils.isEmpty(numbersFromAllSources[i])) { @@ -295,4 +276,16 @@ public class PerSimStatus { return bitmask; } } + + /** Returns true if VoNR is enabled */ + private static boolean isVonrEnabled(Phone phone) { + TelephonyManager telephonyManager = + phone.getContext() + .getSystemService(TelephonyManager.class); + if (telephonyManager == null) { + return false; + } + telephonyManager = telephonyManager.createForSubscriptionId(phone.getSubId()); + return telephonyManager.isVoNrEnabled(); + } } diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java index eef88d2457e1958c13676f62106935152025e838..5a21bafd4931f687cbe49ead5c0fb6a3d49f689d 100644 --- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java +++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java @@ -42,11 +42,18 @@ import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStat import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination; import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms; import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2; +import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms; import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms; import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms; import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent; import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats; import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender; import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats; import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse; import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats; @@ -159,6 +166,13 @@ public class PersistAtomsStorage { /** Maximum number of GBA Event to store between pulls. */ private final int mMaxNumGbaEventStats; + /** Maximum number of outgoing short code sms to store between pulls. */ + private final int mMaxOutgoingShortCodeSms; + + /** Maximum number of Satellite relevant stats to store between pulls. */ + private final int mMaxNumSatelliteStats; + private final int mMaxNumSatelliteControllerStats = 1; + /** Stores persist atoms and persist states of the puller. */ @VisibleForTesting protected PersistAtoms mAtoms; @@ -207,6 +221,8 @@ public class PersistAtomsStorage { mMaxNumUceEventStats = 5; mMaxNumPresenceNotifyEventStats = 10; mMaxNumGbaEventStats = 5; + mMaxOutgoingShortCodeSms = 5; + mMaxNumSatelliteStats = 5; } else { mMaxNumVoiceCallSessions = 50; mMaxNumSms = 25; @@ -229,6 +245,8 @@ public class PersistAtomsStorage { mMaxNumUceEventStats = 25; mMaxNumPresenceNotifyEventStats = 50; mMaxNumGbaEventStats = 10; + mMaxOutgoingShortCodeSms = 10; + mMaxNumSatelliteStats = 15; } mAtoms = loadAtomsFromFile(); @@ -431,6 +449,14 @@ public class PersistAtomsStorage { } } + /** + * Store the number of times auto data switch feature is toggled. + */ + public synchronized void recordToggledAutoDataSwitch() { + mAtoms.autoDataSwitchToggleCount++; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + /** Adds a new {@link NetworkRequestsV2} to the storage. */ public synchronized void addNetworkRequestsV2(NetworkRequestsV2 networkRequests) { NetworkRequestsV2 existingMetrics = find(networkRequests); @@ -655,6 +681,113 @@ public class PersistAtomsStorage { } } + /** Adds an outgoing short code sms to the storage. */ + public synchronized void addOutgoingShortCodeSms(OutgoingShortCodeSms shortCodeSms) { + OutgoingShortCodeSms existingOutgoingShortCodeSms = find(shortCodeSms); + if (existingOutgoingShortCodeSms != null) { + existingOutgoingShortCodeSms.shortCodeSmsCount += 1; + } else { + mAtoms.outgoingShortCodeSms = insertAtRandomPlace(mAtoms.outgoingShortCodeSms, + shortCodeSms, mMaxOutgoingShortCodeSms); + } + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + + /** Adds a new {@link SatelliteController} to the storage. */ + public synchronized void addSatelliteControllerStats(SatelliteController stats) { + // SatelliteController is a single data point + SatelliteController[] atomArray = mAtoms.satelliteController; + if (atomArray == null || atomArray.length == 0) { + atomArray = new SatelliteController[] {new SatelliteController()}; + } + + SatelliteController atom = atomArray[0]; + atom.countOfSatelliteServiceEnablementsSuccess + += stats.countOfSatelliteServiceEnablementsSuccess; + atom.countOfSatelliteServiceEnablementsFail + += stats.countOfSatelliteServiceEnablementsFail; + atom.countOfOutgoingDatagramSuccess + += stats.countOfOutgoingDatagramSuccess; + atom.countOfOutgoingDatagramFail + += stats.countOfOutgoingDatagramFail; + atom.countOfIncomingDatagramSuccess + += stats.countOfIncomingDatagramSuccess; + atom.countOfIncomingDatagramFail + += stats.countOfIncomingDatagramFail; + atom.countOfDatagramTypeSosSmsSuccess + += stats.countOfDatagramTypeSosSmsSuccess; + atom.countOfDatagramTypeSosSmsFail + += stats.countOfDatagramTypeSosSmsFail; + atom.countOfDatagramTypeLocationSharingSuccess + += stats.countOfDatagramTypeLocationSharingSuccess; + atom.countOfDatagramTypeLocationSharingFail + += stats.countOfDatagramTypeLocationSharingFail; + atom.countOfProvisionSuccess + += stats.countOfProvisionSuccess; + atom.countOfProvisionFail + += stats.countOfProvisionFail; + atom.countOfDeprovisionSuccess + += stats.countOfDeprovisionSuccess; + atom.countOfDeprovisionFail + += stats.countOfDeprovisionFail; + atom.totalServiceUptimeSec + += stats.totalServiceUptimeSec; + atom.totalBatteryConsumptionPercent + += stats.totalBatteryConsumptionPercent; + atom.totalBatteryChargedTimeSec + += stats.totalBatteryChargedTimeSec; + + mAtoms.satelliteController = atomArray; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + + /** Adds a new {@link SatelliteSession} to the storage. */ + public synchronized void addSatelliteSessionStats(SatelliteSession stats) { + SatelliteSession existingStats = find(stats); + if (existingStats != null) { + existingStats.count += 1; + } else { + mAtoms.satelliteSession = + insertAtRandomPlace(mAtoms.satelliteSession, stats, mMaxNumSatelliteStats); + } + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + + /** Adds a new {@link SatelliteIncomingDatagram} to the storage. */ + public synchronized void addSatelliteIncomingDatagramStats(SatelliteIncomingDatagram stats) { + mAtoms.satelliteIncomingDatagram = + insertAtRandomPlace(mAtoms.satelliteIncomingDatagram, stats, mMaxNumSatelliteStats); + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + + /** Adds a new {@link SatelliteOutgoingDatagram} to the storage. */ + public synchronized void addSatelliteOutgoingDatagramStats(SatelliteOutgoingDatagram stats) { + mAtoms.satelliteOutgoingDatagram = + insertAtRandomPlace(mAtoms.satelliteOutgoingDatagram, stats, mMaxNumSatelliteStats); + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + + /** Adds a new {@link SatelliteProvision} to the storage. */ + public synchronized void addSatelliteProvisionStats(SatelliteProvision stats) { + mAtoms.satelliteProvision = + insertAtRandomPlace(mAtoms.satelliteProvision, stats, mMaxNumSatelliteStats); + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + + /** Adds a new {@link SatelliteSosMessageRecommender} to the storage. */ + public synchronized void addSatelliteSosMessageRecommenderStats( + SatelliteSosMessageRecommender stats) { + SatelliteSosMessageRecommender existingStats = find(stats); + if (existingStats != null) { + existingStats.count += 1; + } else { + mAtoms.satelliteSosMessageRecommender = + insertAtRandomPlace(mAtoms.satelliteSosMessageRecommender, stats, + mMaxNumSatelliteStats); + } + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + /** * Returns and clears the voice call sessions if last pulled longer than {@code * minIntervalMillis} ago, otherwise returns {@code null}. @@ -865,6 +998,16 @@ public class PersistAtomsStorage { } } + /** @return the number of times auto data switch mobile data policy is toggled. */ + public synchronized int getAutoDataSwitchToggleCount() { + int count = mAtoms.autoDataSwitchToggleCount; + if (count > 0) { + mAtoms.autoDataSwitchToggleCount = 0; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + } + return count; + } + /** * Returns and clears the ImsRegistrationFeatureTagStats if last pulled longer than * {@code minIntervalMillis} ago, otherwise returns {@code null}. @@ -1174,6 +1317,135 @@ public class PersistAtomsStorage { return bitmask; } + /** + * Returns and clears the OutgoingShortCodeSms if last pulled longer than {@code + * minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized OutgoingShortCodeSms[] getOutgoingShortCodeSms(long minIntervalMillis) { + if ((getWallTimeMillis() - mAtoms.outgoingShortCodeSmsPullTimestampMillis) + > minIntervalMillis) { + mAtoms.outgoingShortCodeSmsPullTimestampMillis = getWallTimeMillis(); + OutgoingShortCodeSms[] previousOutgoingShortCodeSms = mAtoms.outgoingShortCodeSms; + mAtoms.outgoingShortCodeSms = new OutgoingShortCodeSms[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return previousOutgoingShortCodeSms; + } else { + return null; + } + } + + /** + * Returns and clears the {@link SatelliteController} stats if last pulled longer than {@code + * minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized SatelliteController[] getSatelliteControllerStats(long minIntervalMillis) { + if (getWallTimeMillis() - mAtoms.satelliteControllerPullTimestampMillis + > minIntervalMillis) { + mAtoms.satelliteControllerPullTimestampMillis = getWallTimeMillis(); + SatelliteController[] statsArray = mAtoms.satelliteController; + mAtoms.satelliteController = new SatelliteController[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return statsArray; + } else { + return null; + } + } + + /** + * Returns and clears the {@link SatelliteSession} stats if last pulled longer than {@code + * minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized SatelliteSession[] getSatelliteSessionStats(long minIntervalMillis) { + if (getWallTimeMillis() - mAtoms.satelliteSessionPullTimestampMillis + > minIntervalMillis) { + mAtoms.satelliteSessionPullTimestampMillis = getWallTimeMillis(); + SatelliteSession[] statsArray = mAtoms.satelliteSession; + mAtoms.satelliteSession = new SatelliteSession[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return statsArray; + } else { + return null; + } + } + + /** + * Returns and clears the {@link SatelliteIncomingDatagram} stats if last pulled longer than + * {@code minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized SatelliteIncomingDatagram[] getSatelliteIncomingDatagramStats( + long minIntervalMillis) { + if (getWallTimeMillis() - mAtoms.satelliteIncomingDatagramPullTimestampMillis + > minIntervalMillis) { + mAtoms.satelliteIncomingDatagramPullTimestampMillis = getWallTimeMillis(); + SatelliteIncomingDatagram[] statsArray = mAtoms.satelliteIncomingDatagram; + mAtoms.satelliteIncomingDatagram = new SatelliteIncomingDatagram[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return statsArray; + } else { + return null; + } + } + + /** + * Returns and clears the {@link SatelliteOutgoingDatagram} stats if last pulled longer than + * {@code minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized SatelliteOutgoingDatagram[] getSatelliteOutgoingDatagramStats( + long minIntervalMillis) { + if (getWallTimeMillis() - mAtoms.satelliteOutgoingDatagramPullTimestampMillis + > minIntervalMillis) { + mAtoms.satelliteOutgoingDatagramPullTimestampMillis = getWallTimeMillis(); + SatelliteOutgoingDatagram[] statsArray = mAtoms.satelliteOutgoingDatagram; + mAtoms.satelliteOutgoingDatagram = new SatelliteOutgoingDatagram[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return statsArray; + } else { + return null; + } + } + + /** + * Returns and clears the {@link SatelliteProvision} stats if last pulled longer than {@code + * minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized SatelliteProvision[] getSatelliteProvisionStats(long minIntervalMillis) { + if (getWallTimeMillis() - mAtoms.satelliteProvisionPullTimestampMillis + > minIntervalMillis) { + mAtoms.satelliteProvisionPullTimestampMillis = getWallTimeMillis(); + SatelliteProvision[] statsArray = mAtoms.satelliteProvision; + mAtoms.satelliteProvision = new SatelliteProvision[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return statsArray; + } else { + return null; + } + } + + /** + * Returns and clears the {@link SatelliteSosMessageRecommender} stats if last pulled longer + * than {@code minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized SatelliteSosMessageRecommender[] getSatelliteSosMessageRecommenderStats( + long minIntervalMillis) { + if (getWallTimeMillis() - mAtoms.satelliteSosMessageRecommenderPullTimestampMillis + > minIntervalMillis) { + mAtoms.satelliteProvisionPullTimestampMillis = getWallTimeMillis(); + SatelliteSosMessageRecommender[] statsArray = mAtoms.satelliteSosMessageRecommender; + mAtoms.satelliteSosMessageRecommender = new SatelliteSosMessageRecommender[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return statsArray; + } else { + return null; + } + } + /** Saves {@link PersistAtoms} to a file in private storage immediately. */ public synchronized void flushAtoms() { saveAtomsToFile(0); @@ -1309,6 +1581,21 @@ public class PersistAtomsStorage { atoms.unmeteredNetworks, UnmeteredNetworks.class ); + atoms.outgoingShortCodeSms = sanitizeAtoms(atoms.outgoingShortCodeSms, + OutgoingShortCodeSms.class, mMaxOutgoingShortCodeSms); + atoms.satelliteController = sanitizeAtoms(atoms.satelliteController, + SatelliteController.class, mMaxNumSatelliteControllerStats); + atoms.satelliteSession = sanitizeAtoms(atoms.satelliteSession, + SatelliteSession.class, mMaxNumSatelliteStats); + atoms.satelliteIncomingDatagram = sanitizeAtoms(atoms.satelliteIncomingDatagram, + SatelliteIncomingDatagram.class, mMaxNumSatelliteStats); + atoms.satelliteOutgoingDatagram = sanitizeAtoms(atoms.satelliteOutgoingDatagram, + SatelliteOutgoingDatagram.class, mMaxNumSatelliteStats); + atoms.satelliteProvision = sanitizeAtoms(atoms.satelliteProvision, + SatelliteProvision.class, mMaxNumSatelliteStats); + atoms.satelliteSosMessageRecommender = sanitizeAtoms( + atoms.satelliteSosMessageRecommender, SatelliteSosMessageRecommender.class, + mMaxNumSatelliteStats); // out of caution, sanitize also the timestamps atoms.voiceCallRatUsagePullTimestampMillis = @@ -1357,7 +1644,20 @@ public class PersistAtomsStorage { sanitizeTimestamp(atoms.presenceNotifyEventPullTimestampMillis); atoms.gbaEventPullTimestampMillis = sanitizeTimestamp(atoms.gbaEventPullTimestampMillis); - + atoms.outgoingShortCodeSmsPullTimestampMillis = + sanitizeTimestamp(atoms.outgoingShortCodeSmsPullTimestampMillis); + atoms.satelliteControllerPullTimestampMillis = + sanitizeTimestamp(atoms.satelliteControllerPullTimestampMillis); + atoms.satelliteSessionPullTimestampMillis = + sanitizeTimestamp(atoms.satelliteSessionPullTimestampMillis); + atoms.satelliteIncomingDatagramPullTimestampMillis = + sanitizeTimestamp(atoms.satelliteIncomingDatagramPullTimestampMillis); + atoms.satelliteOutgoingDatagramPullTimestampMillis = + sanitizeTimestamp(atoms.satelliteOutgoingDatagramPullTimestampMillis); + atoms.satelliteProvisionPullTimestampMillis = + sanitizeTimestamp(atoms.satelliteProvisionPullTimestampMillis); + atoms.satelliteSosMessageRecommenderPullTimestampMillis = + sanitizeTimestamp(atoms.satelliteSosMessageRecommenderPullTimestampMillis); return atoms; } catch (NoSuchFileException e) { Rlog.d(TAG, "PersistAtoms file not found"); @@ -1407,7 +1707,9 @@ public class PersistAtomsStorage { && state.simSlotIndex == key.simSlotIndex && state.isMultiSim == key.isMultiSim && state.carrierId == key.carrierId - && state.isEmergencyOnly == key.isEmergencyOnly) { + && state.isEmergencyOnly == key.isEmergencyOnly + && state.isInternetPdnUp == key.isInternetPdnUp + && state.foldState == key.foldState) { return state; } } @@ -1724,6 +2026,53 @@ public class PersistAtomsStorage { return null; } + /** + * Returns OutgoingShortCodeSms atom that has same category, xmlVersion as the given one, + * or {@code null} if it does not exist. + */ + private @Nullable OutgoingShortCodeSms find(OutgoingShortCodeSms key) { + for (OutgoingShortCodeSms shortCodeSms : mAtoms.outgoingShortCodeSms) { + if (shortCodeSms.category == key.category + && shortCodeSms.xmlVersion == key.xmlVersion) { + return shortCodeSms; + } + } + return null; + } + + /** + * Returns SatelliteOutgoingDatagram atom that has same values or {@code null} + * if it does not exist. + */ + private @Nullable SatelliteSession find( + SatelliteSession key) { + for (SatelliteSession stats : mAtoms.satelliteSession) { + if (stats.satelliteServiceInitializationResult + == key.satelliteServiceInitializationResult + && stats.satelliteTechnology == key.satelliteTechnology) { + return stats; + } + } + return null; + } + + /** + * Returns SatelliteOutgoingDatagram atom that has same values or {@code null} + * if it does not exist. + */ + private @Nullable SatelliteSosMessageRecommender find( + SatelliteSosMessageRecommender key) { + for (SatelliteSosMessageRecommender stats : mAtoms.satelliteSosMessageRecommender) { + if (stats.isDisplaySosMessageSent == key.isDisplaySosMessageSent + && stats.countOfTimerStarted == key.countOfTimerStarted + && stats.isImsRegistered == key.isImsRegistered + && stats.cellularServiceState == key.cellularServiceState) { + return stats; + } + } + return null; + } + /** * Inserts a new element in a random position in an array with a maximum size. * @@ -1969,6 +2318,13 @@ public class PersistAtomsStorage { atoms.uceEventStatsPullTimestampMillis = currentTime; atoms.presenceNotifyEventPullTimestampMillis = currentTime; atoms.gbaEventPullTimestampMillis = currentTime; + atoms.outgoingShortCodeSmsPullTimestampMillis = currentTime; + atoms.satelliteControllerPullTimestampMillis = currentTime; + atoms.satelliteSessionPullTimestampMillis = currentTime; + atoms.satelliteIncomingDatagramPullTimestampMillis = currentTime; + atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime; + atoms.satelliteProvisionPullTimestampMillis = currentTime; + atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime; Rlog.d(TAG, "created new PersistAtoms"); return atoms; diff --git a/src/java/com/android/internal/telephony/metrics/RcsStats.java b/src/java/com/android/internal/telephony/metrics/RcsStats.java index a9191b66112319be1224b9b2d490a3459643fc71..8d24defb2b963d0934923283e2c5387a1701b060 100644 --- a/src/java/com/android/internal/telephony/metrics/RcsStats.java +++ b/src/java/com/android/internal/telephony/metrics/RcsStats.java @@ -85,6 +85,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; @@ -129,48 +130,51 @@ public class RcsStats { private static final Map FEATURE_TAGS = new HashMap<>(); static { - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_STANDALONE_MSG.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_STANDALONE_MSG.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_STANDALONE_MSG); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_IM.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_IM.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CHAT_IM); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_SESSION.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_SESSION.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CHAT_SESSION); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_FILE_TRANSFER); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER_VIA_SMS.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER_VIA_SMS.trim() + .toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_FILE_TRANSFER_VIA_SMS); FEATURE_TAGS.put( - FeatureTags.FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING.trim().toLowerCase(), + FeatureTags.FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING.trim() + .toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING); FEATURE_TAGS.put( - FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY.trim().toLowerCase(), + FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_POST_CALL.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_POST_CALL.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_POST_CALL); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_MAP.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_MAP.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_SHARED_MAP); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_SKETCH.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_SKETCH.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_SHARED_SKETCH); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_GEO_PUSH); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH_VIA_SMS.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH_VIA_SMS.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_GEO_PUSH_VIA_SMS); FEATURE_TAGS.put( - FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION.trim().toLowerCase(), + FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION.trim() + .toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION); String FeatureTag = FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG; - FEATURE_TAGS.put(FeatureTag.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTag.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG); FEATURE_TAGS.put( - FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED.trim().toLowerCase(), + FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_VERSION_SUPPORTED); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHATBOT_ROLE.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHATBOT_ROLE.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_ROLE); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_MMTEL.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_MMTEL.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_MMTEL); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_VIDEO.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_VIDEO.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_VIDEO); - FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_PRESENCE.trim().toLowerCase(), + FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_PRESENCE.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_PRESENCE); } @@ -183,34 +187,42 @@ public class RcsStats { private static final Map SERVICE_IDS = new HashMap<>(); static { - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_MMTEL.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_MMTEL.trim().toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_MMTEL); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V1.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V1.trim().toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHAT_V1); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V2.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V2.trim().toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHAT_V2); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT.trim().toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_FT); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT_OVER_SMS.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT_OVER_SMS.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_FT_OVER_SMS); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH.trim().toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_GEO_PUSH); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH_VIA_SMS.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH_VIA_SMS.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_GEO_PUSH_VIA_SMS); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CALL_COMPOSER.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CALL_COMPOSER.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CALL_COMPOSER); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_POST_CALL.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_POST_CALL.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_POST_CALL); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_MAP.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_MAP.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_SHARED_MAP); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_SKETCH.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_SKETCH.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_SHARED_SKETCH); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT.trim().toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHATBOT); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_STANDALONE.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_STANDALONE.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHATBOT_STANDALONE ); - SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_ROLE.trim().toLowerCase(), + SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_ROLE.trim() + .toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHATBOT_ROLE); } @@ -221,33 +233,33 @@ public class RcsStats { private static final Map MESSAGE_TYPE = new HashMap<>(); static { - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INVITE.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INVITE.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_INVITE); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_ACK.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_ACK.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_ACK); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_OPTIONS.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_OPTIONS.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_OPTIONS); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_BYE.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_BYE.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_BYE); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_CANCEL.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_CANCEL.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_CANCEL); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REGISTER.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REGISTER.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_REGISTER); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PRACK.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PRACK.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_PRACK); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_SUBSCRIBE.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_SUBSCRIBE.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_SUBSCRIBE); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_NOTIFY.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_NOTIFY.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_NOTIFY); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PUBLISH.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PUBLISH.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_PUBLISH); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INFO.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INFO.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_INFO); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REFER.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REFER.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_REFER); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_MESSAGE.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_MESSAGE.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_MESSAGE); - MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_UPDATE.trim().toLowerCase(), + MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_UPDATE.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_UPDATE); } @@ -1570,28 +1582,28 @@ public class RcsStats { /** Get a enum value from pre-defined feature tag name list */ @VisibleForTesting public int convertTagNameToValue(@NonNull String tagName) { - return FEATURE_TAGS.getOrDefault(tagName.trim().toLowerCase(), + return FEATURE_TAGS.getOrDefault(tagName.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.IMS_FEATURE_TAG_CUSTOM); } /** Get a enum value from pre-defined service id list */ @VisibleForTesting public int convertServiceIdToValue(@NonNull String serviceId) { - return SERVICE_IDS.getOrDefault(serviceId.trim().toLowerCase(), + return SERVICE_IDS.getOrDefault(serviceId.trim().toLowerCase(Locale.ROOT), IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CUSTOM); } /** Get a enum value from pre-defined message type list */ @VisibleForTesting public int convertMessageTypeToValue(@NonNull String messageType) { - return MESSAGE_TYPE.getOrDefault(messageType.trim().toLowerCase(), + return MESSAGE_TYPE.getOrDefault(messageType.trim().toLowerCase(Locale.ROOT), TelephonyProtoEnums.SIP_REQUEST_CUSTOM); } /** Get a enum value from pre-defined reason list */ @VisibleForTesting public int convertPresenceNotifyReason(@NonNull String reason) { - return NOTIFY_REASONS.getOrDefault(reason.trim().toLowerCase(), + return NOTIFY_REASONS.getOrDefault(reason.trim().toLowerCase(Locale.ROOT), PRESENCE_NOTIFY_EVENT__REASON__REASON_CUSTOM); } diff --git a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java new file mode 100644 index 0000000000000000000000000000000000000000..7ff370c252b33e9a0c88e4a51ce4faf1ac92a074 --- /dev/null +++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java @@ -0,0 +1,905 @@ +/* + * 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 com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender; +import com.android.telephony.Rlog; + +/** Tracks Satellite metrics for each phone */ +public class SatelliteStats { + private static final String TAG = SatelliteStats.class.getSimpleName(); + + private final PersistAtomsStorage mAtomsStorage = + PhoneFactory.getMetricsCollector().getAtomsStorage(); + + private static SatelliteStats sInstance = null; + + /** Gets the instance of SatelliteStats */ + public static SatelliteStats getInstance() { + if (sInstance == null) { + Rlog.d(TAG, "SatelliteStats created."); + synchronized (SatelliteStats.class) { + sInstance = new SatelliteStats(); + } + } + return sInstance; + } + + /** + * A data class to contain whole component of {@link SatelliteController) atom. + * Refer to {@link #onSatelliteControllerMetrics(SatelliteControllerParams)}. + */ + public class SatelliteControllerParams { + private final int mCountOfSatelliteServiceEnablementsSuccess; + private final int mCountOfSatelliteServiceEnablementsFail; + private final int mCountOfOutgoingDatagramSuccess; + private final int mCountOfOutgoingDatagramFail; + private final int mCountOfIncomingDatagramSuccess; + private final int mCountOfIncomingDatagramFail; + private final int mCountOfDatagramTypeSosSmsSuccess; + private final int mCountOfDatagramTypeSosSmsFail; + private final int mCountOfDatagramTypeLocationSharingSuccess; + private final int mCountOfDatagramTypeLocationSharingFail; + private final int mCountOfProvisionSuccess; + private final int mCountOfProvisionFail; + private final int mCountOfDeprovisionSuccess; + private final int mCountOfDeprovisionFail; + private final int mTotalServiceUptimeSec; + private final int mTotalBatteryConsumptionPercent; + private final int mTotalBatteryChargedTimeSec; + + private SatelliteControllerParams(Builder builder) { + this.mCountOfSatelliteServiceEnablementsSuccess = + builder.mCountOfSatelliteServiceEnablementsSuccess; + this.mCountOfSatelliteServiceEnablementsFail = + builder.mCountOfSatelliteServiceEnablementsFail; + this.mCountOfOutgoingDatagramSuccess = builder.mCountOfOutgoingDatagramSuccess; + this.mCountOfOutgoingDatagramFail = builder.mCountOfOutgoingDatagramFail; + this.mCountOfIncomingDatagramSuccess = builder.mCountOfIncomingDatagramSuccess; + this.mCountOfIncomingDatagramFail = builder.mCountOfIncomingDatagramFail; + this.mCountOfDatagramTypeSosSmsSuccess = builder.mCountOfDatagramTypeSosSmsSuccess; + this.mCountOfDatagramTypeSosSmsFail = builder.mCountOfDatagramTypeSosSmsFail; + this.mCountOfDatagramTypeLocationSharingSuccess = + builder.mCountOfDatagramTypeLocationSharingSuccess; + this.mCountOfDatagramTypeLocationSharingFail = + builder.mCountOfDatagramTypeLocationSharingFail; + this.mCountOfProvisionSuccess = builder.mCountOfProvisionSuccess; + this.mCountOfProvisionFail = builder.mCountOfProvisionFail; + this.mCountOfDeprovisionSuccess = builder.mCountOfDeprovisionSuccess; + this.mCountOfDeprovisionFail = builder.mCountOfDeprovisionFail; + this.mTotalServiceUptimeSec = builder.mTotalServiceUptimeSec; + this.mTotalBatteryConsumptionPercent = builder.mTotalBatteryConsumptionPercent; + this.mTotalBatteryChargedTimeSec = builder.mTotalBatteryChargedTimeSec; + } + + public int getCountOfSatelliteServiceEnablementsSuccess() { + return mCountOfSatelliteServiceEnablementsSuccess; + } + + public int getCountOfSatelliteServiceEnablementsFail() { + return mCountOfSatelliteServiceEnablementsFail; + } + + public int getCountOfOutgoingDatagramSuccess() { + return mCountOfOutgoingDatagramSuccess; + } + + public int getCountOfOutgoingDatagramFail() { + return mCountOfOutgoingDatagramFail; + } + + public int getCountOfIncomingDatagramSuccess() { + return mCountOfIncomingDatagramSuccess; + } + + public int getCountOfIncomingDatagramFail() { + return mCountOfIncomingDatagramFail; + } + + public int getCountOfDatagramTypeSosSmsSuccess() { + return mCountOfDatagramTypeSosSmsSuccess; + } + + public int getCountOfDatagramTypeSosSmsFail() { + return mCountOfDatagramTypeSosSmsFail; + } + + public int getCountOfDatagramTypeLocationSharingSuccess() { + return mCountOfDatagramTypeLocationSharingSuccess; + } + + public int getCountOfDatagramTypeLocationSharingFail() { + return mCountOfDatagramTypeLocationSharingFail; + } + + public int getCountOfProvisionSuccess() { + return mCountOfProvisionSuccess; + } + + public int getCountOfProvisionFail() { + return mCountOfProvisionFail; + } + + public int getCountOfDeprovisionSuccess() { + return mCountOfDeprovisionSuccess; + } + + public int getCountOfDeprovisionFail() { + return mCountOfDeprovisionFail; + } + + public int getTotalServiceUptimeSec() { + return mTotalServiceUptimeSec; + } + + public int getTotalBatteryConsumptionPercent() { + return mTotalBatteryConsumptionPercent; + } + + public int getTotalBatteryChargedTimeSec() { + return mTotalBatteryChargedTimeSec; + } + + /** + * A builder class to create {@link SatelliteControllerParams} data structure class + */ + public static class Builder { + private int mCountOfSatelliteServiceEnablementsSuccess = 0; + private int mCountOfSatelliteServiceEnablementsFail = 0; + private int mCountOfOutgoingDatagramSuccess = 0; + private int mCountOfOutgoingDatagramFail = 0; + private int mCountOfIncomingDatagramSuccess = 0; + private int mCountOfIncomingDatagramFail = 0; + private int mCountOfDatagramTypeSosSmsSuccess = 0; + private int mCountOfDatagramTypeSosSmsFail = 0; + private int mCountOfDatagramTypeLocationSharingSuccess = 0; + private int mCountOfDatagramTypeLocationSharingFail = 0; + private int mCountOfProvisionSuccess; + private int mCountOfProvisionFail; + private int mCountOfDeprovisionSuccess; + private int mCountOfDeprovisionFail; + private int mTotalServiceUptimeSec = 0; + private int mTotalBatteryConsumptionPercent = 0; + private int mTotalBatteryChargedTimeSec = 0; + + /** + * Sets countOfSatelliteServiceEnablementsSuccess value of {@link SatelliteController} + * atom then returns Builder class + */ + public Builder setCountOfSatelliteServiceEnablementsSuccess( + int countOfSatelliteServiceEnablementsSuccess) { + this.mCountOfSatelliteServiceEnablementsSuccess = + countOfSatelliteServiceEnablementsSuccess; + return this; + } + + /** + * Sets countOfSatelliteServiceEnablementsFail value of {@link SatelliteController} atom + * then returns Builder class + */ + public Builder setCountOfSatelliteServiceEnablementsFail( + int countOfSatelliteServiceEnablementsFail) { + this.mCountOfSatelliteServiceEnablementsFail = + countOfSatelliteServiceEnablementsFail; + return this; + } + + /** + * Sets countOfOutgoingDatagramSuccess value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setCountOfOutgoingDatagramSuccess(int countOfOutgoingDatagramSuccess) { + this.mCountOfOutgoingDatagramSuccess = countOfOutgoingDatagramSuccess; + return this; + } + + /** + * Sets countOfOutgoingDatagramFail value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setCountOfOutgoingDatagramFail(int countOfOutgoingDatagramFail) { + this.mCountOfOutgoingDatagramFail = countOfOutgoingDatagramFail; + return this; + } + + /** + * Sets countOfIncomingDatagramSuccess value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setCountOfIncomingDatagramSuccess(int countOfIncomingDatagramSuccess) { + this.mCountOfIncomingDatagramSuccess = countOfIncomingDatagramSuccess; + return this; + } + + /** + * Sets countOfIncomingDatagramFail value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setCountOfIncomingDatagramFail(int countOfIncomingDatagramFail) { + this.mCountOfIncomingDatagramFail = countOfIncomingDatagramFail; + return this; + } + + /** + * Sets countOfDatagramTypeSosSmsSuccess value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setCountOfDatagramTypeSosSmsSuccess( + int countOfDatagramTypeSosSmsSuccess) { + this.mCountOfDatagramTypeSosSmsSuccess = countOfDatagramTypeSosSmsSuccess; + return this; + } + + /** + * Sets countOfDatagramTypeSosSmsFail value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setCountOfDatagramTypeSosSmsFail(int countOfDatagramTypeSosSmsFail) { + this.mCountOfDatagramTypeSosSmsFail = countOfDatagramTypeSosSmsFail; + return this; + } + + /** + * Sets countOfDatagramTypeLocationSharingSuccess value of {@link SatelliteController} + * atom then returns Builder class + */ + public Builder setCountOfDatagramTypeLocationSharingSuccess( + int countOfDatagramTypeLocationSharingSuccess) { + this.mCountOfDatagramTypeLocationSharingSuccess = + countOfDatagramTypeLocationSharingSuccess; + return this; + } + + /** + * Sets countOfDatagramTypeLocationSharingFail value of {@link SatelliteController} + * atom then returns Builder class + */ + public Builder setCountOfDatagramTypeLocationSharingFail( + int countOfDatagramTypeLocationSharingFail) { + this.mCountOfDatagramTypeLocationSharingFail = + countOfDatagramTypeLocationSharingFail; + return this; + } + + /** + * Sets countOfProvisionSuccess value of {@link SatelliteController} + * atom then returns Builder class + */ + public Builder setCountOfProvisionSuccess(int countOfProvisionSuccess) { + this.mCountOfProvisionSuccess = countOfProvisionSuccess; + return this; + } + + /** + * Sets countOfProvisionFail value of {@link SatelliteController} + * atom then returns Builder class + */ + public Builder setCountOfProvisionFail(int countOfProvisionFail) { + this.mCountOfProvisionFail = countOfProvisionFail; + return this; + } + + /** + * Sets countOfDeprovisionSuccess value of {@link SatelliteController} + * atom then returns Builder class + */ + public Builder setCountOfDeprovisionSuccess(int countOfDeprovisionSuccess) { + this.mCountOfDeprovisionSuccess = countOfDeprovisionSuccess; + return this; + } + + /** + * Sets countOfDeprovisionSuccess value of {@link SatelliteController} + * atom then returns Builder class + */ + public Builder setCountOfDeprovisionFail(int countOfDeprovisionFail) { + this.mCountOfDeprovisionFail = countOfDeprovisionFail; + return this; + } + + /** + * Sets totalServiceUptimeSec value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setTotalServiceUptimeSec(int totalServiceUptimeSec) { + this.mTotalServiceUptimeSec = totalServiceUptimeSec; + return this; + } + + /** + * Sets totalBatteryConsumptionPercent value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setTotalBatteryConsumptionPercent(int totalBatteryConsumptionPercent) { + this.mTotalBatteryConsumptionPercent = totalBatteryConsumptionPercent; + return this; + } + + /** + * Sets totalBatteryChargedTimeSec value of {@link SatelliteController} atom then + * returns Builder class + */ + public Builder setTotalBatteryChargedTimeSec(int totalBatteryChargedTimeSec) { + this.mTotalBatteryChargedTimeSec = totalBatteryChargedTimeSec; + return this; + } + + /** + * Returns ControllerParams, which contains whole component of + * {@link SatelliteController} atom + */ + public SatelliteControllerParams build() { + return new SatelliteStats() + .new SatelliteControllerParams(this); + } + } + + @Override + public String toString() { + return "ControllerParams(" + + ", countOfSatelliteServiceEnablementsSuccess=" + + mCountOfSatelliteServiceEnablementsSuccess + + ", countOfSatelliteServiceEnablementsFail=" + + mCountOfSatelliteServiceEnablementsFail + + ", countOfOutgoingDatagramSuccess=" + mCountOfOutgoingDatagramSuccess + + ", countOfOutgoingDatagramFail=" + mCountOfOutgoingDatagramFail + + ", countOfIncomingDatagramSuccess=" + mCountOfIncomingDatagramSuccess + + ", countOfIncomingDatagramFail=" + mCountOfIncomingDatagramFail + + ", countOfDatagramTypeSosSms=" + mCountOfDatagramTypeSosSmsSuccess + + ", countOfDatagramTypeSosSms=" + mCountOfDatagramTypeSosSmsFail + + ", countOfDatagramTypeLocationSharing=" + + mCountOfDatagramTypeLocationSharingSuccess + + ", countOfDatagramTypeLocationSharing=" + + mCountOfDatagramTypeLocationSharingFail + + ", serviceUptimeSec=" + mTotalServiceUptimeSec + + ", batteryConsumptionPercent=" + mTotalBatteryConsumptionPercent + + ", batteryChargedTimeSec=" + mTotalBatteryChargedTimeSec + + ")"; + } + } + + /** + * A data class to contain whole component of {@link SatelliteSession) atom. + * Refer to {@link #onSatelliteSessionMetrics(SatelliteSessionParams)}. + */ + public class SatelliteSessionParams { + private final int mSatelliteServiceInitializationResult; + private final int mSatelliteTechnology; + + private SatelliteSessionParams(Builder builder) { + this.mSatelliteServiceInitializationResult = + builder.mSatelliteServiceInitializationResult; + this.mSatelliteTechnology = builder.mSatelliteTechnology; + } + + public int getSatelliteServiceInitializationResult() { + return mSatelliteServiceInitializationResult; + } + + public int getSatelliteTechnology() { + return mSatelliteTechnology; + } + + /** + * A builder class to create {@link SatelliteSessionParams} data structure class + */ + public static class Builder { + private int mSatelliteServiceInitializationResult = -1; + private int mSatelliteTechnology = -1; + + /** + * Sets satelliteServiceInitializationResult value of {@link SatelliteSession} + * atom then returns Builder class + */ + public Builder setSatelliteServiceInitializationResult( + int satelliteServiceInitializationResult) { + this.mSatelliteServiceInitializationResult = satelliteServiceInitializationResult; + return this; + } + + /** + * Sets satelliteTechnology value of {@link SatelliteSession} atoms then + * returns Builder class + */ + public Builder setSatelliteTechnology(int satelliteTechnology) { + this.mSatelliteTechnology = satelliteTechnology; + return this; + } + + /** + * Returns SessionParams, which contains whole component of + * {@link SatelliteSession} atom + */ + public SatelliteSessionParams build() { + return new SatelliteStats() + .new SatelliteSessionParams(this); + } + } + + @Override + public String toString() { + return "SessionParams(" + + ", satelliteServiceInitializationResult=" + + mSatelliteServiceInitializationResult + + ", satelliteTechnology=" + mSatelliteTechnology + + ")"; + } + } + + /** + * A data class to contain whole component of {@link SatelliteIncomingDatagram} atom. + * Refer to {@link #onSatelliteIncomingDatagramMetrics(SatelliteIncomingDatagramParams)}. + */ + public class SatelliteIncomingDatagramParams { + private final int mResultCode; + private final int mDatagramSizeBytes; + private final long mDatagramTransferTimeMillis; + + private SatelliteIncomingDatagramParams(Builder builder) { + this.mResultCode = builder.mResultCode; + this.mDatagramSizeBytes = builder.mDatagramSizeBytes; + this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis; + } + + public int getResultCode() { + return mResultCode; + } + + public int getDatagramSizeBytes() { + return mDatagramSizeBytes; + } + + public long getDatagramTransferTimeMillis() { + return mDatagramTransferTimeMillis; + } + + /** + * A builder class to create {@link SatelliteIncomingDatagramParams} data structure class + */ + public static class Builder { + private int mResultCode = -1; + private int mDatagramSizeBytes = -1; + private long mDatagramTransferTimeMillis = -1; + + /** + * Sets resultCode value of {@link SatelliteIncomingDatagram} atom + * then returns Builder class + */ + public Builder setResultCode(int resultCode) { + this.mResultCode = resultCode; + return this; + } + + /** + * Sets datagramSizeBytes value of {@link SatelliteIncomingDatagram} atom + * then returns Builder class + */ + public Builder setDatagramSizeBytes(int datagramSizeBytes) { + this.mDatagramSizeBytes = datagramSizeBytes; + return this; + } + + /** + * Sets datagramTransferTimeMillis value of {@link SatelliteIncomingDatagram} atom + * then returns Builder class + */ + public Builder setDatagramTransferTimeMillis(long datagramTransferTimeMillis) { + this.mDatagramTransferTimeMillis = datagramTransferTimeMillis; + return this; + } + + /** + * Returns IncomingDatagramParams, which contains whole component of + * {@link SatelliteIncomingDatagram} atom + */ + public SatelliteIncomingDatagramParams build() { + return new SatelliteStats() + .new SatelliteIncomingDatagramParams(Builder.this); + } + } + + @Override + public String toString() { + return "IncomingDatagramParams(" + + ", resultCode=" + mResultCode + + ", datagramSizeBytes=" + mDatagramSizeBytes + + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis + ")"; + } + } + + /** + * A data class to contain whole component of {@link SatelliteOutgoingDatagram} atom. + * Refer to {@link #onSatelliteOutgoingDatagramMetrics(SatelliteOutgoingDatagramParams)}. + */ + public class SatelliteOutgoingDatagramParams { + private final int mDatagramType; + private final int mResultCode; + private final int mDatagramSizeBytes; + private final long mDatagramTransferTimeMillis; + + private SatelliteOutgoingDatagramParams(Builder builder) { + this.mDatagramType = builder.mDatagramType; + this.mResultCode = builder.mResultCode; + this.mDatagramSizeBytes = builder.mDatagramSizeBytes; + this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis; + } + + public int getDatagramType() { + return mDatagramType; + } + + public int getResultCode() { + return mResultCode; + } + + public int getDatagramSizeBytes() { + return mDatagramSizeBytes; + } + + public long getDatagramTransferTimeMillis() { + return mDatagramTransferTimeMillis; + } + + /** + * A builder class to create {@link SatelliteOutgoingDatagramParams} data structure class + */ + public static class Builder { + private int mDatagramType = -1; + private int mResultCode = -1; + private int mDatagramSizeBytes = -1; + private long mDatagramTransferTimeMillis = -1; + + /** + * Sets datagramType value of {@link SatelliteOutgoingDatagram} atom + * then returns Builder class + */ + public Builder setDatagramType(int datagramType) { + this.mDatagramType = datagramType; + return this; + } + + /** + * Sets resultCode value of {@link SatelliteOutgoingDatagram} atom + * then returns Builder class + */ + public Builder setResultCode(int resultCode) { + this.mResultCode = resultCode; + return this; + } + + /** + * Sets datagramSizeBytes value of {@link SatelliteOutgoingDatagram} atom + * then returns Builder class + */ + public Builder setDatagramSizeBytes(int datagramSizeBytes) { + this.mDatagramSizeBytes = datagramSizeBytes; + return this; + } + + /** + * Sets datagramTransferTimeMillis value of {@link SatelliteOutgoingDatagram} atom + * then returns Builder class + */ + public Builder setDatagramTransferTimeMillis(long datagramTransferTimeMillis) { + this.mDatagramTransferTimeMillis = datagramTransferTimeMillis; + return this; + } + + /** + * Returns OutgoingDatagramParams, which contains whole component of + * {@link SatelliteOutgoingDatagram} atom + */ + public SatelliteOutgoingDatagramParams build() { + return new SatelliteStats() + .new SatelliteOutgoingDatagramParams(Builder.this); + } + } + + @Override + public String toString() { + return "OutgoingDatagramParams(" + + "datagramType=" + mDatagramType + + ", resultCode=" + mResultCode + + ", datagramSizeBytes=" + mDatagramSizeBytes + + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis + ")"; + } + } + + /** + * A data class to contain whole component of {@link SatelliteProvision} atom. + * Refer to {@link #onSatelliteProvisionMetrics(SatelliteProvisionParams)}. + */ + public class SatelliteProvisionParams { + private final int mResultCode; + private final int mProvisioningTimeSec; + private final boolean mIsProvisionRequest; + private final boolean mIsCanceled; + + private SatelliteProvisionParams(Builder builder) { + this.mResultCode = builder.mResultCode; + this.mProvisioningTimeSec = builder.mProvisioningTimeSec; + this.mIsProvisionRequest = builder.mIsProvisionRequest; + this.mIsCanceled = builder.mIsCanceled; + } + + public int getResultCode() { + return mResultCode; + } + + public int getProvisioningTimeSec() { + return mProvisioningTimeSec; + } + + public boolean getIsProvisionRequest() { + return mIsProvisionRequest; + } + + public boolean getIsCanceled() { + return mIsCanceled; + } + + /** + * A builder class to create {@link SatelliteProvisionParams} data structure class + */ + public static class Builder { + private int mResultCode = -1; + private int mProvisioningTimeSec = -1; + private boolean mIsProvisionRequest = false; + private boolean mIsCanceled = false; + + /** + * Sets resultCode value of {@link SatelliteProvision} atom + * then returns Builder class + */ + public Builder setResultCode(int resultCode) { + this.mResultCode = resultCode; + return this; + } + + /** + * Sets provisioningTimeSec value of {@link SatelliteProvision} atom + * then returns Builder class + */ + public Builder setProvisioningTimeSec(int provisioningTimeSec) { + this.mProvisioningTimeSec = provisioningTimeSec; + return this; + } + + /** + * Sets isProvisionRequest value of {@link SatelliteProvision} atom + * then returns Builder class + */ + public Builder setIsProvisionRequest(boolean isProvisionRequest) { + this.mIsProvisionRequest = isProvisionRequest; + return this; + } + + /** + * Sets isCanceled value of {@link SatelliteProvision} atom + * then returns Builder class + */ + public Builder setIsCanceled(boolean isCanceled) { + this.mIsCanceled = isCanceled; + return this; + } + + /** + * Returns ProvisionParams, which contains whole component of + * {@link SatelliteProvision} atom + */ + public SatelliteProvisionParams build() { + return new SatelliteStats() + .new SatelliteProvisionParams(Builder.this); + } + } + + @Override + public String toString() { + return "ProvisionParams(" + + "resultCode=" + mResultCode + + ", provisioningTimeSec=" + mProvisioningTimeSec + + ", isProvisionRequest=" + mIsProvisionRequest + + ", isCanceled" + mIsCanceled + ")"; + } + } + + /** + * A data class to contain whole component of {@link SatelliteSosMessageRecommender} atom. + * Refer to {@link #onSatelliteSosMessageRecommender(SatelliteSosMessageRecommenderParams)}. + */ + public class SatelliteSosMessageRecommenderParams { + private final boolean mIsDisplaySosMessageSent; + private final int mCountOfTimerStarted; + private final boolean mIsImsRegistered; + private final int mCellularServiceState; + + private SatelliteSosMessageRecommenderParams(Builder builder) { + this.mIsDisplaySosMessageSent = builder.mIsDisplaySosMessageSent; + this.mCountOfTimerStarted = builder.mCountOfTimerStarted; + this.mIsImsRegistered = builder.mIsImsRegistered; + this.mCellularServiceState = builder.mCellularServiceState; + } + + public boolean isDisplaySosMessageSent() { + return mIsDisplaySosMessageSent; + } + + public int getCountOfTimerStarted() { + return mCountOfTimerStarted; + } + + public boolean isImsRegistered() { + return mIsImsRegistered; + } + + public int getCellularServiceState() { + return mCellularServiceState; + } + + /** + * A builder class to create {@link SatelliteProvisionParams} data structure class + */ + public static class Builder { + private boolean mIsDisplaySosMessageSent = false; + private int mCountOfTimerStarted = -1; + private boolean mIsImsRegistered = false; + private int mCellularServiceState = -1; + + /** + * Sets resultCode value of {@link SatelliteSosMessageRecommender} atom + * then returns Builder class + */ + public Builder setDisplaySosMessageSent( + boolean isDisplaySosMessageSent) { + this.mIsDisplaySosMessageSent = isDisplaySosMessageSent; + return this; + } + + /** + * Sets countOfTimerIsStarted value of {@link SatelliteSosMessageRecommender} atom + * then returns Builder class + */ + public Builder setCountOfTimerStarted(int countOfTimerStarted) { + this.mCountOfTimerStarted = countOfTimerStarted; + return this; + } + + /** + * Sets isImsRegistered value of {@link SatelliteSosMessageRecommender} atom + * then returns Builder class + */ + public Builder setImsRegistered(boolean isImsRegistered) { + this.mIsImsRegistered = isImsRegistered; + return this; + } + + /** + * Sets cellularServiceState value of {@link SatelliteSosMessageRecommender} atom + * then returns Builder class + */ + public Builder setCellularServiceState(int cellularServiceState) { + this.mCellularServiceState = cellularServiceState; + return this; + } + + /** + * Returns SosMessageRecommenderParams, which contains whole component of + * {@link SatelliteSosMessageRecommenderParams} atom + */ + public SatelliteSosMessageRecommenderParams build() { + return new SatelliteStats() + .new SatelliteSosMessageRecommenderParams(Builder.this); + } + } + + @Override + public String toString() { + return "SosMessageRecommenderParams(" + + "isDisplaySosMessageSent=" + mIsDisplaySosMessageSent + + ", countOfTimerStarted=" + mCountOfTimerStarted + + ", isImsRegistered=" + mIsImsRegistered + + ", cellularServiceState=" + mCellularServiceState + ")"; + } + } + + /** Create a new atom or update an existing atom for SatelliteController metrics */ + public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) { + SatelliteController proto = new SatelliteController(); + proto.countOfSatelliteServiceEnablementsSuccess = + param.getCountOfSatelliteServiceEnablementsSuccess(); + proto.countOfSatelliteServiceEnablementsFail = + param.getCountOfSatelliteServiceEnablementsFail(); + proto.countOfOutgoingDatagramSuccess = param.getCountOfOutgoingDatagramSuccess(); + proto.countOfOutgoingDatagramFail = param.getCountOfOutgoingDatagramFail(); + proto.countOfIncomingDatagramSuccess = param.getCountOfIncomingDatagramSuccess(); + proto.countOfIncomingDatagramFail = param.getCountOfIncomingDatagramFail(); + proto.countOfDatagramTypeSosSmsSuccess = param.getCountOfDatagramTypeSosSmsSuccess(); + proto.countOfDatagramTypeSosSmsFail = param.getCountOfDatagramTypeSosSmsFail(); + proto.countOfDatagramTypeLocationSharingSuccess = + param.getCountOfDatagramTypeLocationSharingSuccess(); + proto.countOfDatagramTypeLocationSharingFail = + param.getCountOfDatagramTypeLocationSharingFail(); + proto.countOfProvisionSuccess = param.getCountOfProvisionSuccess(); + proto.countOfProvisionFail = param.getCountOfProvisionFail(); + proto.countOfDeprovisionSuccess = param.getCountOfDeprovisionSuccess(); + proto.countOfDeprovisionFail = param.getCountOfDeprovisionFail(); + proto.totalServiceUptimeSec = param.getTotalServiceUptimeSec(); + proto.totalBatteryConsumptionPercent = param.getTotalBatteryConsumptionPercent(); + proto.totalBatteryChargedTimeSec = param.getTotalBatteryChargedTimeSec(); + + mAtomsStorage.addSatelliteControllerStats(proto); + } + + /** Create a new atom or update an existing atom for SatelliteSession metrics */ + public synchronized void onSatelliteSessionMetrics(SatelliteSessionParams param) { + SatelliteSession proto = new SatelliteSession(); + proto.satelliteServiceInitializationResult = + param.getSatelliteServiceInitializationResult(); + proto.satelliteTechnology = param.getSatelliteTechnology(); + proto.count = 1; + mAtomsStorage.addSatelliteSessionStats(proto); + } + + /** Create a new atom for SatelliteIncomingDatagram metrics */ + public synchronized void onSatelliteIncomingDatagramMetrics( + SatelliteIncomingDatagramParams param) { + SatelliteIncomingDatagram proto = new SatelliteIncomingDatagram(); + proto.resultCode = param.getResultCode(); + proto.datagramSizeBytes = param.getDatagramSizeBytes(); + proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis(); + mAtomsStorage.addSatelliteIncomingDatagramStats(proto); + } + + /** Create a new atom for SatelliteOutgoingDatagram metrics */ + public synchronized void onSatelliteOutgoingDatagramMetrics( + SatelliteOutgoingDatagramParams param) { + SatelliteOutgoingDatagram proto = new SatelliteOutgoingDatagram(); + proto.datagramType = param.getDatagramType(); + proto.resultCode = param.getResultCode(); + proto.datagramSizeBytes = param.getDatagramSizeBytes(); + proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis(); + mAtomsStorage.addSatelliteOutgoingDatagramStats(proto); + } + + /** Create a new atom for SatelliteProvision metrics */ + public synchronized void onSatelliteProvisionMetrics(SatelliteProvisionParams param) { + SatelliteProvision proto = new SatelliteProvision(); + proto.resultCode = param.getResultCode(); + proto.provisioningTimeSec = param.getProvisioningTimeSec(); + proto.isProvisionRequest = param.getIsProvisionRequest(); + proto.isCanceled = param.getIsCanceled(); + mAtomsStorage.addSatelliteProvisionStats(proto); + } + + /** Create a new atom or update an existing atom for SatelliteSosMessageRecommender metrics */ + public synchronized void onSatelliteSosMessageRecommender( + SatelliteSosMessageRecommenderParams param) { + SatelliteSosMessageRecommender proto = new SatelliteSosMessageRecommender(); + proto.isDisplaySosMessageSent = param.isDisplaySosMessageSent(); + proto.countOfTimerStarted = param.getCountOfTimerStarted(); + proto.isImsRegistered = param.isImsRegistered(); + proto.cellularServiceState = param.getCellularServiceState(); + proto.count = 1; + mAtomsStorage.addSatelliteSosMessageRecommenderStats(proto); + } +} diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java index 9d9088eee147f7a3a700aa7d973d95e835a87ff2..b830cd00d26e3016d69962be67cf86f52b1260a8 100644 --- a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java +++ b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java @@ -15,7 +15,13 @@ */ 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; +import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; +import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.SystemClock; import android.telephony.AccessNetworkConstants; @@ -25,33 +31,36 @@ import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; -import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; -import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; -import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.ServiceStateTracker; +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.imsphone.ImsPhone; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; import com.android.telephony.Rlog; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** Tracks service state duration and switch metrics for each phone. */ -public class ServiceStateStats { +public class ServiceStateStats extends DataNetworkControllerCallback { private static final String TAG = ServiceStateStats.class.getSimpleName(); private final AtomicReference mLastState = new AtomicReference<>(new TimestampedServiceState(null, 0L)); private final Phone mPhone; private final PersistAtomsStorage mStorage; + private final DeviceStateHelper mDeviceStateHelper; public ServiceStateStats(Phone phone) { + super(Runnable::run); mPhone = phone; mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage(); + mDeviceStateHelper = PhoneFactory.getMetricsCollector().getDeviceStateHelper(); } /** Finalizes the durations of the current service state segment. */ @@ -80,6 +89,21 @@ public class ServiceStateStats { addServiceState(lastState, now); } + /** Registers for internet pdn connected callback. */ + public void registerDataNetworkControllerCallback() { + mPhone.getDataNetworkController().registerDataNetworkControllerCallback(this); + } + + /** Updates service state when internet pdn gets connected. */ + public void onInternetDataNetworkConnected(@NonNull List internetNetworks) { + onInternetDataNetworkChanged(true); + } + + /** Updates service state when internet pdn gets disconnected. */ + public void onInternetDataNetworkDisconnected() { + onInternetDataNetworkChanged(false); + } + /** Updates the current service state. */ public void onServiceStateChanged(ServiceState serviceState) { final long now = getTimeMillis(); @@ -97,7 +121,8 @@ public class ServiceStateStats { newState.isMultiSim = SimSlotState.isMultiSim(); newState.carrierId = mPhone.getCarrierId(); newState.isEmergencyOnly = isEmergencyOnly(serviceState); - + newState.isInternetPdnUp = isInternetPdnUp(mPhone); + newState.foldState = mDeviceStateHelper.getFoldState(); TimestampedServiceState prevState = mLastState.getAndSet(new TimestampedServiceState(newState, now)); addServiceStateAndSwitch( @@ -105,6 +130,26 @@ public class ServiceStateStats { } } + /** Updates the fold state of the device for the current service state. */ + public void onFoldStateChanged(int foldState) { + final long now = getTimeMillis(); + CellularServiceState lastServiceState = mLastState.get().mServiceState; + if (lastServiceState == null || lastServiceState.foldState == foldState) { + // Not need to update the fold state if modem is off or if is the + // same fold state + return; + } else { + TimestampedServiceState lastState = + mLastState.getAndUpdate( + state -> { + CellularServiceState newServiceState = copyOf(state.mServiceState); + newServiceState.foldState = foldState; + return new TimestampedServiceState(newServiceState, now); + }); + addServiceState(lastState, now); + } + } + private void addServiceState(TimestampedServiceState prevState, long now) { addServiceStateAndSwitch(prevState, now, null); } @@ -224,6 +269,8 @@ public class ServiceStateStats { copy.carrierId = state.carrierId; copy.totalTimeMillis = state.totalTimeMillis; copy.isEmergencyOnly = state.isEmergencyOnly; + copy.isInternetPdnUp = state.isInternetPdnUp; + copy.foldState = state.foldState; return copy; } @@ -310,6 +357,29 @@ public class ServiceStateStats { || nrState == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED; } + private static boolean isInternetPdnUp(Phone phone) { + DataNetworkController dataNetworkController = phone.getDataNetworkController(); + if (dataNetworkController != null) { + return dataNetworkController.getInternetDataNetworkState() == DATA_CONNECTED; + } + return false; + } + + private void onInternetDataNetworkChanged(boolean internetPdnUp) { + final long now = getTimeMillis(); + TimestampedServiceState lastState = + mLastState.getAndUpdate( + state -> { + if (state.mServiceState == null) { + return new TimestampedServiceState(null, now); + } + CellularServiceState newServiceState = copyOf(state.mServiceState); + newServiceState.isInternetPdnUp = internetPdnUp; + return new TimestampedServiceState(newServiceState, now); + }); + addServiceState(lastState, now); + } + @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 48826fdd9d641ece11cc7cc5dbb97f885ff4ee72..2f1e6a71828abb55f3feef7014692efa785eb5b1 100644 --- a/src/java/com/android/internal/telephony/metrics/SmsStats.java +++ b/src/java/com/android/internal/telephony/metrics/SmsStats.java @@ -58,6 +58,7 @@ import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms; +import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms; import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms; import com.android.telephony.Rlog; @@ -155,46 +156,62 @@ public class SmsStats { /** Create a new atom when an outgoing SMS is sent. */ public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs, - @SmsManager.Result int errorCode, long messageId, boolean isFromDefaultApp, + @SmsManager.Result int sendErrorCode, long messageId, boolean isFromDefaultApp, long intervalMillis) { - onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, errorCode, NO_ERROR_CODE, + onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, sendErrorCode, NO_ERROR_CODE, messageId, isFromDefaultApp, intervalMillis); } /** Create a new atom when an outgoing SMS is sent. */ public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs, - @SmsManager.Result int errorCode, int radioSpecificErrorCode, long messageId, + @SmsManager.Result int sendErrorCode, int networkErrorCode, long messageId, boolean isFromDefaultApp, long intervalMillis) { OutgoingSms proto = getOutgoingDefaultProto(is3gpp2, isOverIms, messageId, isFromDefaultApp, intervalMillis); + // The field errorCode is used for up-to-Android-13 devices. From Android 14, sendErrorCode + // and networkErrorCode will be used. The field errorCode will be deprecated when most + // devices use Android 14 or higher versions. if (isOverIms) { // Populate error code and result for IMS case - proto.errorCode = errorCode; + proto.errorCode = sendErrorCode; if (fallbackToCs) { proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK; - } else if (errorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) { + } else if (sendErrorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) { proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY; - } else if (errorCode != SmsManager.RESULT_ERROR_NONE) { + } else if (sendErrorCode != SmsManager.RESULT_ERROR_NONE) { proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR; } } else { // Populate error code and result for CS case - if (errorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) { + if (sendErrorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) { proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY; - } else if (errorCode != SmsManager.RESULT_ERROR_NONE) { + } else if (sendErrorCode != SmsManager.RESULT_ERROR_NONE) { proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR; } - proto.errorCode = radioSpecificErrorCode; - if (errorCode == SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE - && radioSpecificErrorCode == NO_ERROR_CODE) { + proto.errorCode = networkErrorCode; + if (sendErrorCode == SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE + && networkErrorCode == NO_ERROR_CODE) { proto.errorCode = is3gpp2 ? NO_NETWORK_ERROR_3GPP2 : NO_NETWORK_ERROR_3GPP; } } + + proto.sendErrorCode = sendErrorCode; + proto.networkErrorCode = networkErrorCode; + mAtomsStorage.addOutgoingSms(proto); } + /** Create a new atom when user attempted to send an outgoing short code sms. */ + public void onOutgoingShortCodeSms(int category, int xmlVersion) { + OutgoingShortCodeSms proto = new OutgoingShortCodeSms(); + proto.category = category; + proto.xmlVersion = xmlVersion; + proto.shortCodeSmsCount = 1; + mAtomsStorage.addOutgoingShortCodeSms(proto); + } + /** Creates a proto for a normal single-part {@code IncomingSms} with default values. */ private IncomingSms getIncomingDefaultProto(boolean is3gpp2, @InboundSmsHandler.SmsSource int smsSource) { @@ -216,6 +233,7 @@ public class SmsStats { // SMS messages (e.g. those handled by OS or error cases). proto.messageId = RANDOM.nextLong(); proto.count = 1; + proto.isManagedProfile = mPhone.isManagedProfile(); return proto; } @@ -241,6 +259,7 @@ public class SmsStats { proto.retryId = 0; proto.intervalMillis = intervalMillis; proto.count = 1; + proto.isManagedProfile = mPhone.isManagedProfile(); return proto; } diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java index 7d68ae147574c0a09b2189c3650a8babcd23ced7..ba07fa035abd4404d83eb53608705765f775fca7 100644 --- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java +++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java @@ -46,6 +46,7 @@ import android.telecom.VideoProfile.VideoState; import android.telephony.Annotation.NetworkType; import android.telephony.AnomalyReporter; import android.telephony.DisconnectCause; +import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.telephony.ims.ImsReasonInfo; @@ -63,6 +64,7 @@ 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.imsphone.ImsPhone; import com.android.internal.telephony.imsphone.ImsPhoneConnection; import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession; import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec; @@ -155,6 +157,8 @@ public class VoiceCallSessionStats { private final PersistAtomsStorage mAtomsStorage = PhoneFactory.getMetricsCollector().getAtomsStorage(); private final UiccController mUiccController = UiccController.getInstance(); + private final DeviceStateHelper mDeviceStateHelper = + PhoneFactory.getMetricsCollector().getDeviceStateHelper(); public VoiceCallSessionStats(int phoneId, Phone phone) { mPhoneId = phoneId; @@ -366,9 +370,8 @@ public class VoiceCallSessionStats { proto.srvccCompleted = true; proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; // Call RAT may have changed (e.g. IWLAN -> UMTS) due to bearer change - proto.ratAtEnd = - ServiceStateStats.getVoiceRat( - mPhone, mPhone.getServiceState(), proto.bearerAtEnd); + updateRatAtEnd(proto, getVoiceRatWithVoNRFix( + mPhone, mPhone.getServiceState(), proto.bearerAtEnd)); } break; case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED: @@ -422,8 +425,7 @@ public class VoiceCallSessionStats { } int bearer = getBearer(conn); ServiceState serviceState = getServiceState(); - @NetworkType int rat = ServiceStateStats.getVoiceRat(mPhone, serviceState, bearer); - + @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, serviceState, bearer); VoiceCallSession proto = new VoiceCallSession(); proto.bearerAtStart = bearer; @@ -446,7 +448,7 @@ public class VoiceCallSessionStats { proto.srvccFailureCount = 0L; proto.srvccCancellationCount = 0L; proto.rttEnabled = false; - proto.isEmergency = conn.isEmergencyCall(); + proto.isEmergency = conn.isEmergencyCall() || conn.isNetworkIdentifiedEmergencyCall(); proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false; proto.isMultiparty = conn.isMultiparty(); proto.lastKnownRat = rat; @@ -512,15 +514,10 @@ public class VoiceCallSessionStats { } // Update end RAT - @NetworkType - int rat = ServiceStateStats.getVoiceRat(mPhone, getServiceState(), proto.bearerAtEnd); - if (proto.ratAtEnd != rat) { - proto.ratSwitchCount++; - proto.ratAtEnd = rat; - if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { - proto.lastKnownRat = rat; - } - } + updateRatAtEnd(proto, getVoiceRatWithVoNRFix(mPhone, getServiceState(), proto.bearerAtEnd)); + + // Set device fold state + proto.foldState = mDeviceStateHelper.getFoldState(); mAtomsStorage.addVoiceCallSession(proto); @@ -581,8 +578,7 @@ public class VoiceCallSessionStats { proto.setupFailed = false; // Track RAT when voice call is connected. ServiceState serviceState = getServiceState(); - proto.ratAtConnected = - ServiceStateStats.getVoiceRat(mPhone, serviceState, proto.bearerAtEnd); + proto.ratAtConnected = getVoiceRatWithVoNRFix(mPhone, serviceState, proto.bearerAtEnd); // Reset list of codecs with the last codec at the present time. In this way, we // track codec quality only after call is connected and not while ringing. resetCodecList(conn); @@ -593,19 +589,14 @@ public class VoiceCallSessionStats { // RAT usage is not broken down by bearer. In case a CS call is made while there is IMS // voice registration, this may be inaccurate (i.e. there could be multiple RAT in use, but // we only pick the most feasible one). - @NetworkType int rat = ServiceStateStats.getVoiceRat(mPhone, state); + @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, state, + VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN); mRatUsage.add(mPhone.getCarrierId(), rat, getTimeMillis(), getConnectionIds()); for (int i = 0; i < mCallProtos.size(); i++) { VoiceCallSession proto = mCallProtos.valueAt(i); - rat = ServiceStateStats.getVoiceRat(mPhone, state, proto.bearerAtEnd); - if (proto.ratAtEnd != rat) { - proto.ratSwitchCount++; - proto.ratAtEnd = rat; - if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { - proto.lastKnownRat = rat; - } - } + rat = getVoiceRatWithVoNRFix(mPhone, state, proto.bearerAtEnd); + updateRatAtEnd(proto, rat); proto.bandAtEnd = (rat == TelephonyManager.NETWORK_TYPE_IWLAN) ? 0 : ServiceStateStats.getBand(state); @@ -613,6 +604,16 @@ public class VoiceCallSessionStats { } } + private void updateRatAtEnd(VoiceCallSession proto, @NetworkType int rat) { + if (proto.ratAtEnd != rat) { + proto.ratSwitchCount++; + proto.ratAtEnd = rat; + if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { + proto.lastKnownRat = rat; + } + } + } + private void finishImsCall(int id, ImsReasonInfo reasonInfo, long durationMillis) { VoiceCallSession proto = mCallProtos.get(id); proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; @@ -679,6 +680,43 @@ public class VoiceCallSessionStats { return mPhone.getSignalStrength().getLevel(); } + /** + * This is a copy of ServiceStateStats.getVoiceRat(Phone, ServiceState, int) with minimum fix + * required for tracking EPSFB correctly. + */ + @VisibleForTesting private static @NetworkType int getVoiceRatWithVoNRFix( + Phone phone, @Nullable ServiceState state, int bearer) { + if (state == null) { + return TelephonyManager.NETWORK_TYPE_UNKNOWN; + } + ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); + if (bearer != VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS && imsPhone != null) { + @NetworkType int imsVoiceRat = imsPhone.getImsStats().getImsVoiceRadioTech(); + @NetworkType int wwanPsRat = + ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_PS); + if (imsVoiceRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { + // If IMS is registered over WWAN but WWAN PS is not in service, + // fallback to WWAN CS RAT + boolean isImsVoiceRatValid = + (imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN + || wwanPsRat != TelephonyManager.NETWORK_TYPE_UNKNOWN); + if (isImsVoiceRatValid) { + // Fix for VoNR and EPSFB, b/277906557 + @NetworkType int oldRat = ServiceStateStats.getVoiceRat(phone, state, bearer), + rat = imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN + ? imsVoiceRat : wwanPsRat; + logd("getVoiceRatWithVoNRFix: oldRat=%d, newRat=%d", oldRat, rat); + return rat; + } + } + } + if (bearer == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) { + return TelephonyManager.NETWORK_TYPE_UNKNOWN; + } else { + return ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_CS); + } + } + /** Resets the list of codecs used for the connection with only the codec currently in use. */ private void resetCodecList(Connection conn) { int id = getConnectionId(conn); diff --git a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java index 81151278b7262b09979f838a3338f145f8f933b5..8b34933749b1fac067c19848bdf3e505d3043175 100644 --- a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java +++ b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.nitz; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.time.UnixEpochTime; import android.app.timedetector.TelephonyTimeSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.Context; @@ -372,7 +373,7 @@ public final class NitzStateMachineImpl implements NitzStateMachine { builder.addDebugInfo("Clearing time suggestion" + " reason=" + reason); } else { - TimestampedValue newNitzTime = nitzSignal.createTimeSignal(); + UnixEpochTime newNitzTime = nitzSignal.createTimeSignal(); builder.setUnixEpochTime(newNitzTime); builder.addDebugInfo("Sending new time suggestion" + " nitzSignal=" + nitzSignal diff --git a/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java b/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java index 9c7aac9cfccdf0fa85b65fab55f33275e9fe4733..74b30f81c82ae76b40ddac98235a35d7bd86f0b0 100644 --- a/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java +++ b/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java @@ -18,13 +18,13 @@ package com.android.internal.telephony.nitz; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.time.UnixEpochTime; import android.app.timedetector.TelephonyTimeSuggestion; import android.app.timedetector.TimeDetector; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.app.timezonedetector.TimeZoneDetector; import android.content.Context; import android.os.SystemClock; -import android.os.TimestampedValue; import android.util.LocalLog; import com.android.internal.telephony.Phone; @@ -69,8 +69,9 @@ public final class TimeServiceHelperImpl implements TimeServiceHelper { Objects.requireNonNull(timeSuggestion); if (timeSuggestion.getUnixEpochTime() != null) { - TimestampedValue unixEpochTime = timeSuggestion.getUnixEpochTime(); - TelephonyMetrics.getInstance().writeNITZEvent(mSlotIndex, unixEpochTime.getValue()); + UnixEpochTime unixEpochTime = timeSuggestion.getUnixEpochTime(); + TelephonyMetrics.getInstance().writeNITZEvent( + mSlotIndex, unixEpochTime.getUnixEpochTimeMillis()); } mTimeDetector.suggestTelephonyTime(timeSuggestion); } diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java new file mode 100644 index 0000000000000000000000000000000000000000..e7f09c23ed578b2c8a7aaccc109d607d79f5e8d2 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java @@ -0,0 +1,379 @@ +/* + * 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.content.Context; +import android.os.Build; +import android.os.Looper; +import android.os.SystemProperties; +import android.telephony.Rlog; +import android.telephony.satellite.ISatelliteDatagramCallback; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Datagram controller used for sending and receiving satellite datagrams. + */ +public class DatagramController { + private static final String TAG = "DatagramController"; + + @NonNull private static DatagramController sInstance; + @NonNull private final Context mContext; + @NonNull private final PointingAppController mPointingAppController; + @NonNull private final DatagramDispatcher mDatagramDispatcher; + @NonNull private final DatagramReceiver mDatagramReceiver; + public static final long MAX_DATAGRAM_ID = (long) Math.pow(2, 16); + public static final int ROUNDING_UNIT = 10; + public static final long SATELLITE_ALIGN_TIMEOUT = TimeUnit.SECONDS.toMillis(30); + private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; + private static final boolean DEBUG = !"user".equals(Build.TYPE); + + /** Variables used to update onSendDatagramStateChanged(). */ + private final Object mLock = new Object(); + @GuardedBy("mLock") + private int mSendSubId; + @GuardedBy("mLock") + private @SatelliteManager.SatelliteDatagramTransferState int mSendDatagramTransferState = + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE; + @GuardedBy("mLock") + private int mSendPendingCount = 0; + @GuardedBy("mLock") + private int mSendErrorCode = SatelliteManager.SATELLITE_ERROR_NONE; + /** Variables used to update onReceiveDatagramStateChanged(). */ + @GuardedBy("mLock") + private int mReceiveSubId; + @GuardedBy("mLock") + private @SatelliteManager.SatelliteDatagramTransferState int mReceiveDatagramTransferState = + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE; + @GuardedBy("mLock") + private int mReceivePendingCount = 0; + @GuardedBy("mLock") + private int mReceiveErrorCode = SatelliteManager.SATELLITE_ERROR_NONE; + + private SatelliteDatagram mDemoModeDatagram; + private boolean mIsDemoMode = false; + private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT; + + /** + * @return The singleton instance of DatagramController. + */ + public static DatagramController getInstance() { + if (sInstance == null) { + loge("DatagramController was not yet initialized."); + } + return sInstance; + } + + /** + * Create the DatagramController singleton instance. + * @param context The Context to use to create the DatagramController. + * @param looper The looper for the handler. + * @param pointingAppController PointingAppController is used to update + * PointingApp about datagram transfer state changes. + * @return The singleton instance of DatagramController. + */ + public static DatagramController make(@NonNull Context context, @NonNull Looper looper, + @NonNull PointingAppController pointingAppController) { + if (sInstance == null) { + sInstance = new DatagramController(context, looper, pointingAppController); + } + return sInstance; + } + + /** + * Create a DatagramController to send and receive satellite datagrams. + * + * @param context The Context for the DatagramController. + * @param looper The looper for the handler + * @param pointingAppController PointingAppController is used to update PointingApp + * about datagram transfer state changes. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected DatagramController(@NonNull Context context, @NonNull Looper looper, + @NonNull PointingAppController pointingAppController) { + mContext = context; + mPointingAppController = pointingAppController; + + // Create the DatagramDispatcher singleton, + // which is used to send satellite datagrams. + mDatagramDispatcher = DatagramDispatcher.make(mContext, looper, this); + + // Create the DatagramReceiver singleton, + // which is used to receive satellite datagrams. + mDatagramReceiver = DatagramReceiver.make(mContext, looper, this); + } + + /** + * Register to receive incoming datagrams over satellite. + * + * @param subId The subId of the subscription to register for incoming satellite datagrams. + * @param callback The callback to handle incoming datagrams over satellite. + * + * @return The {@link SatelliteManager.SatelliteError} result of the operation. + */ + @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId, + @NonNull ISatelliteDatagramCallback callback) { + return mDatagramReceiver.registerForSatelliteDatagram(subId, callback); + } + + /** + * Unregister to stop receiving incoming datagrams over satellite. + * If callback was not registered before, the request will be ignored. + * + * @param subId The subId of the subscription to unregister for incoming satellite datagrams. + * @param callback The callback that was passed to + * {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}. + */ + public void unregisterForSatelliteDatagram(int subId, + @NonNull ISatelliteDatagramCallback callback) { + mDatagramReceiver.unregisterForSatelliteDatagram(subId, callback); + } + + /** + * Poll pending satellite datagrams over satellite. + * + * This method requests modem to check if there are any pending datagrams to be received over + * satellite. If there are any incoming datagrams, they will be received via + * {@link android.telephony.satellite.SatelliteDatagramCallback#onSatelliteDatagramReceived( + * long, SatelliteDatagram, int, Consumer)} + * + * @param subId The subId of the subscription used for receiving datagrams. + * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request. + */ + public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer callback) { + mDatagramReceiver.pollPendingSatelliteDatagrams(subId, callback); + } + + /** + * Send datagram over satellite. + * + * Gateway encodes SOS message or location sharing message into a datagram and passes it as + * input to this method. Datagram received here will be passed down to modem without any + * encoding or encryption. + * + * When demo mode is on, save the sent datagram and this datagram will be used as a received + * datagram. + * + * @param subId The subId of the subscription to send satellite datagrams for. + * @param datagramType datagram type indicating whether the datagram is of type + * SOS_SMS or LOCATION_SHARING. + * @param datagram encoded gateway datagram which is encrypted by the caller. + * Datagram will be passed down to modem without any encoding or encryption. + * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in + * full screen mode. + * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request. + */ + public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType, + @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, + @NonNull Consumer callback) { + setDemoModeDatagram(datagramType, datagram); + mDatagramDispatcher.sendSatelliteDatagram(subId, datagramType, datagram, + needFullScreenPointingUI, callback); + } + + /** + * Update send status to {@link PointingAppController}. + * + * @param subId The subId of the subscription to send satellite datagrams for + * @param datagramTransferState The new send datagram transfer state. + * @param sendPendingCount number of datagrams that are currently being sent + * @param errorCode If datagram transfer failed, the reason for failure. + */ + public void updateSendStatus(int subId, + @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, + int sendPendingCount, int errorCode) { + synchronized (mLock) { + logd("updateSendStatus" + + " subId: " + subId + + " datagramTransferState: " + datagramTransferState + + " sendPendingCount: " + sendPendingCount + " errorCode: " + errorCode); + + mSendSubId = subId; + mSendDatagramTransferState = datagramTransferState; + mSendPendingCount = sendPendingCount; + mSendErrorCode = errorCode; + + notifyDatagramTransferStateChangedToSessionController(); + mPointingAppController.updateSendDatagramTransferState(mSendSubId, + mSendDatagramTransferState, mSendPendingCount, mSendErrorCode); + } + } + + /** + * Update receive status to {@link PointingAppController}. + * + * @param subId The subId of the subscription used to receive datagrams + * @param datagramTransferState The new receive datagram transfer state. + * @param receivePendingCount The number of datagrams that are currently pending to be received. + * @param errorCode If datagram transfer failed, the reason for failure. + */ + public void updateReceiveStatus(int subId, + @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, + int receivePendingCount, int errorCode) { + synchronized (mLock) { + logd("updateReceiveStatus" + + " subId: " + subId + + " datagramTransferState: " + datagramTransferState + + " receivePendingCount: " + receivePendingCount + " errorCode: " + errorCode); + + mReceiveSubId = subId; + mReceiveDatagramTransferState = datagramTransferState; + mReceivePendingCount = receivePendingCount; + mReceiveErrorCode = errorCode; + + notifyDatagramTransferStateChangedToSessionController(); + mPointingAppController.updateReceiveDatagramTransferState(mReceiveSubId, + mReceiveDatagramTransferState, mReceivePendingCount, mReceiveErrorCode); + } + + if (isPollingInIdleState()) { + mDatagramDispatcher.retrySendingDatagrams(); + } + } + + /** + * Return receive pending datagram count + * @return receive pending datagram count. + */ + public int getReceivePendingCount() { + return mReceivePendingCount; + } + + /** + * This function is used by {@link SatelliteController} to notify {@link DatagramController} + * that satellite modem state has changed. + * + * @param state Current satellite modem state. + */ + public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) { + mDatagramDispatcher.onSatelliteModemStateChanged(state); + mDatagramReceiver.onSatelliteModemStateChanged(state); + } + + void onDeviceAlignedWithSatellite(boolean isAligned) { + mDatagramDispatcher.onDeviceAlignedWithSatellite(isAligned); + mDatagramReceiver.onDeviceAlignedWithSatellite(isAligned); + } + + @VisibleForTesting + public boolean isReceivingDatagrams() { + synchronized (mLock) { + return (mReceiveDatagramTransferState + == SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + } + } + + public boolean isSendingInIdleState() { + synchronized (mLock) { + return mSendDatagramTransferState == + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE; + } + } + + public boolean isPollingInIdleState() { + synchronized (mLock) { + return mReceiveDatagramTransferState == + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE; + } + } + + /** + * Set variables for {@link DatagramDispatcher} and {@link DatagramReceiver} to run demo mode + * @param isDemoMode {@code true} means demo mode is on, {@code false} otherwise. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setDemoMode(boolean isDemoMode) { + mIsDemoMode = isDemoMode; + mDatagramDispatcher.setDemoMode(isDemoMode); + mDatagramReceiver.setDemoMode(isDemoMode); + + if (!isDemoMode) { + mDemoModeDatagram = null; + } + } + + /** Get the last sent datagram for demo mode */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public SatelliteDatagram getDemoModeDatagram() { + return mDemoModeDatagram; + } + + /** + * Set last sent datagram for demo mode + * @param datagramType datagram type, only DATAGRAM_TYPE_SOS_MESSAGE will be saved + * @param datagram datagram The last datagram saved when sendSatelliteDatagramForDemo is called + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected void setDemoModeDatagram(@SatelliteManager.DatagramType int datagramType, + SatelliteDatagram datagram) { + if (mIsDemoMode && datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { + mDemoModeDatagram = datagram; + } + } + + long getSatelliteAlignedTimeoutDuration() { + return mAlignTimeoutDuration; + } + + /** + * This API can be used by only CTS to update the timeout duration in milliseconds whether + * the device is aligned with the satellite for demo mode + * + * @param timeoutMillis The timeout duration in millisecond. + * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise. + */ + boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) { + if (!isMockModemAllowed()) { + loge("Updating align timeout duration is not allowed"); + return false; + } + + logd("setSatelliteDeviceAlignedTimeoutDuration: timeoutMillis=" + timeoutMillis); + mAlignTimeoutDuration = timeoutMillis; + return true; + } + + private boolean isMockModemAllowed() { + return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)); + } + + private void notifyDatagramTransferStateChangedToSessionController() { + SatelliteSessionController sessionController = SatelliteSessionController.getInstance(); + if (sessionController == null) { + loge("notifyDatagramTransferStateChangeToSessionController: SatelliteSessionController" + + " is not initialized yet"); + } else { + sessionController.onDatagramTransferStateChanged( + mSendDatagramTransferState, mReceiveDatagramTransferState); + } + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..77b410db3da49f2ca0fe368f75104645ad66f818 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java @@ -0,0 +1,615 @@ +/* + * 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 com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.metrics.SatelliteStats; +import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; + +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +/** + * Datagram dispatcher used to send satellite datagrams. + */ +public class DatagramDispatcher extends Handler { + private static final String TAG = "DatagramDispatcher"; + + private static final int CMD_SEND_SATELLITE_DATAGRAM = 1; + private static final int EVENT_SEND_SATELLITE_DATAGRAM_DONE = 2; + private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3; + + @NonNull private static DatagramDispatcher sInstance; + @NonNull private final Context mContext; + @NonNull private final DatagramController mDatagramController; + @NonNull private final ControllerMetricsStats mControllerMetricsStats; + + private boolean mIsDemoMode = false; + private boolean mIsAligned = false; + private DatagramDispatcherHandlerRequest mSendSatelliteDatagramRequest = null; + + private static AtomicLong mNextDatagramId = new AtomicLong(0); + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private boolean mSendingDatagramInProgress; + + /** + * Map key: datagramId, value: SendSatelliteDatagramArgument to retry sending emergency + * datagrams. + */ + @GuardedBy("mLock") + private final LinkedHashMap + mPendingEmergencyDatagramsMap = new LinkedHashMap<>(); + + /** + * Map key: datagramId, value: SendSatelliteDatagramArgument to retry sending non-emergency + * datagrams. + */ + @GuardedBy("mLock") + private final LinkedHashMap + mPendingNonEmergencyDatagramsMap = new LinkedHashMap<>(); + + /** + * Create the DatagramDispatcher singleton instance. + * @param context The Context to use to create the DatagramDispatcher. + * @param looper The looper for the handler. + * @param datagramController DatagramController which is used to update datagram transfer state. + * @return The singleton instance of DatagramDispatcher. + */ + public static DatagramDispatcher make(@NonNull Context context, @NonNull Looper looper, + @NonNull DatagramController datagramController) { + if (sInstance == null) { + sInstance = new DatagramDispatcher(context, looper, datagramController); + } + return sInstance; + } + + /** + * Create a DatagramDispatcher to send satellite datagrams. + * + * @param context The Context for the DatagramDispatcher. + * @param looper The looper for the handler. + * @param datagramController DatagramController which is used to update datagram transfer state. + */ + @VisibleForTesting + protected DatagramDispatcher(@NonNull Context context, @NonNull Looper looper, + @NonNull DatagramController datagramController) { + super(looper); + mContext = context; + mDatagramController = datagramController; + mControllerMetricsStats = ControllerMetricsStats.getInstance(); + + synchronized (mLock) { + mSendingDatagramInProgress = false; + } + } + + private static final class DatagramDispatcherHandlerRequest { + /** The argument to use for the request */ + public @NonNull Object argument; + /** The caller needs to specify the phone to be used for the request */ + public @NonNull Phone phone; + /** The result of the request that is run on the main thread */ + public @Nullable Object result; + + DatagramDispatcherHandlerRequest(Object argument, Phone phone) { + this.argument = argument; + this.phone = phone; + } + } + + private static final class SendSatelliteDatagramArgument { + public int subId; + public long datagramId; + public @SatelliteManager.DatagramType int datagramType; + public @NonNull SatelliteDatagram datagram; + public boolean needFullScreenPointingUI; + public @NonNull Consumer callback; + public long datagramStartTime; + public boolean skipCheckingSatelliteAligned = false; + + SendSatelliteDatagramArgument(int subId, long datagramId, + @SatelliteManager.DatagramType int datagramType, + @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, + @NonNull Consumer callback) { + this.subId = subId; + this.datagramId = datagramId; + this.datagramType = datagramType; + this.datagram = datagram; + this.needFullScreenPointingUI = needFullScreenPointingUI; + this.callback = callback; + } + + /** returns the size of outgoing SMS, rounded by 10 bytes */ + public int getDatagramRoundedSizeBytes() { + if (datagram.getSatelliteDatagram() != null) { + int sizeBytes = datagram.getSatelliteDatagram().length; + // rounded by ROUNDING_UNIT + return (int) (Math.round((double) sizeBytes / ROUNDING_UNIT) * ROUNDING_UNIT); + } else { + return 0; + } + } + + /** sets the start time at datagram is sent out */ + public void setDatagramStartTime() { + datagramStartTime = + datagramStartTime == 0 ? System.currentTimeMillis() : datagramStartTime; + } + } + + @Override + public void handleMessage(Message msg) { + DatagramDispatcherHandlerRequest request; + Message onCompleted; + AsyncResult ar; + + switch(msg.what) { + case CMD_SEND_SATELLITE_DATAGRAM: { + logd("CMD_SEND_SATELLITE_DATAGRAM"); + request = (DatagramDispatcherHandlerRequest) msg.obj; + SendSatelliteDatagramArgument argument = + (SendSatelliteDatagramArgument) request.argument; + onCompleted = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request); + + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance().sendSatelliteDatagram(argument.datagram, + argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, + argument.needFullScreenPointingUI, onCompleted); + break; + } + + Phone phone = request.phone; + if (phone != null) { + phone.sendSatelliteDatagram(onCompleted, argument.datagram, + argument.needFullScreenPointingUI); + } else { + loge("sendSatelliteDatagram: No phone object"); + synchronized (mLock) { + // Remove current datagram from pending map + if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { + mPendingEmergencyDatagramsMap.remove(argument.datagramId); + } else { + mPendingNonEmergencyDatagramsMap.remove(argument.datagramId); + } + + // Update send status + mDatagramController.updateSendStatus(argument.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED, + getPendingDatagramCount(), + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + mDatagramController.updateSendStatus(argument.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + 0, SatelliteManager.SATELLITE_ERROR_NONE); + + // report phone == null case + reportSendDatagramCompleted(argument, + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + argument.callback.accept( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + + // Abort sending all the pending datagrams + abortSendingPendingDatagrams(argument.subId, + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + } + break; + } + + case EVENT_SEND_SATELLITE_DATAGRAM_DONE: { + ar = (AsyncResult) msg.obj; + request = (DatagramDispatcherHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, "sendSatelliteDatagram"); + SendSatelliteDatagramArgument argument = + (SendSatelliteDatagramArgument) request.argument; + + synchronized (mLock) { + if (mIsDemoMode && (error == SatelliteManager.SATELLITE_ERROR_NONE)) { + if (argument.skipCheckingSatelliteAligned) { + logd("Satellite was already aligned. No need to check alignment again"); + } else if (!mIsAligned) { + logd("Satellite is not aligned in demo mode, wait for the alignment."); + startSatelliteAlignedTimer(request); + break; + } + } + + logd("EVENT_SEND_SATELLITE_DATAGRAM_DONE error: " + error); + // log metrics about the outgoing datagram + reportSendDatagramCompleted(argument, error); + + mSendingDatagramInProgress = false; + + // Remove current datagram from pending map. + if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { + mPendingEmergencyDatagramsMap.remove(argument.datagramId); + } else { + mPendingNonEmergencyDatagramsMap.remove(argument.datagramId); + } + + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + // Update send status for current datagram + mDatagramController.updateSendStatus(argument.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS, + getPendingDatagramCount(), error); + mControllerMetricsStats.reportOutgoingDatagramSuccessCount( + argument.datagramType); + + if (getPendingDatagramCount() > 0) { + // Send response for current datagram + argument.callback.accept(error); + // Send pending datagrams + sendPendingDatagrams(); + } else { + mDatagramController.updateSendStatus(argument.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + 0, SatelliteManager.SATELLITE_ERROR_NONE); + // Send response for current datagram + argument.callback.accept(error); + } + } else { + // Update send status + mDatagramController.updateSendStatus(argument.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED, + getPendingDatagramCount(), error); + mDatagramController.updateSendStatus(argument.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + 0, SatelliteManager.SATELLITE_ERROR_NONE); + // Send response for current datagram + // after updating datagram transfer state internally. + argument.callback.accept(error); + // Abort sending all the pending datagrams + mControllerMetricsStats.reportOutgoingDatagramFailCount( + argument.datagramType); + abortSendingPendingDatagrams(argument.subId, + SatelliteManager.SATELLITE_REQUEST_ABORTED); + } + } + break; + } + + case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: { + handleEventSatelliteAlignedTimeout((DatagramDispatcherHandlerRequest) msg.obj); + break; + } + + default: + logw("DatagramDispatcherHandler: unexpected message code: " + msg.what); + break; + } + } + + /** + * Send datagram over satellite. + * + * Gateway encodes SOS message or location sharing message into a datagram and passes it as + * input to this method. Datagram received here will be passed down to modem without any + * encoding or encryption. + * + * @param subId The subId of the subscription to send satellite datagrams for. + * @param datagramType datagram type indicating whether the datagram is of type + * SOS_SMS or LOCATION_SHARING. + * @param datagram encoded gateway datagram which is encrypted by the caller. + * Datagram will be passed down to modem without any encoding or encryption. + * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in + * full screen mode. + * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request. + */ + public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType, + @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, + @NonNull Consumer callback) { + Phone phone = SatelliteServiceUtils.getPhone(); + + long datagramId = mNextDatagramId.getAndUpdate( + n -> ((n + 1) % DatagramController.MAX_DATAGRAM_ID)); + + SendSatelliteDatagramArgument datagramArgs = + new SendSatelliteDatagramArgument(subId, datagramId, datagramType, datagram, + needFullScreenPointingUI, callback); + + synchronized (mLock) { + // Add datagram to pending datagram map + if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { + mPendingEmergencyDatagramsMap.put(datagramId, datagramArgs); + } else { + mPendingNonEmergencyDatagramsMap.put(datagramId, datagramArgs); + } + + // Modem can be busy receiving datagrams, so send datagram only when modem is not busy. + if (!mSendingDatagramInProgress && mDatagramController.isPollingInIdleState()) { + mSendingDatagramInProgress = true; + datagramArgs.setDatagramStartTime(); + mDatagramController.updateSendStatus(subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, + getPendingDatagramCount(), SatelliteManager.SATELLITE_ERROR_NONE); + sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArgs, phone); + } + } + } + + public void retrySendingDatagrams() { + synchronized (mLock) { + sendPendingDatagrams(); + } + } + + /** Set demo mode + * + * @param isDemoMode {@code true} means demo mode is on, {@code false} otherwise. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected void setDemoMode(boolean isDemoMode) { + mIsDemoMode = isDemoMode; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected void onDeviceAlignedWithSatellite(boolean isAligned) { + if (mIsDemoMode) { + synchronized (mLock) { + mIsAligned = isAligned; + if (isAligned) handleEventSatelliteAligned(); + } + } + } + + private void startSatelliteAlignedTimer(@NonNull DatagramDispatcherHandlerRequest request) { + if (isSatelliteAlignedTimerStarted()) { + logd("Satellite aligned timer was already started"); + return; + } + mSendSatelliteDatagramRequest = request; + sendMessageDelayed( + obtainMessage(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT, request), + getSatelliteAlignedTimeoutDuration()); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected long getSatelliteAlignedTimeoutDuration() { + return mDatagramController.getSatelliteAlignedTimeoutDuration(); + } + + private void handleEventSatelliteAligned() { + if (isSatelliteAlignedTimerStarted()) { + stopSatelliteAlignedTimer(); + + if (mSendSatelliteDatagramRequest == null) { + loge("handleEventSatelliteAligned: mSendSatelliteDatagramRequest is null"); + } else { + SendSatelliteDatagramArgument argument = + (SendSatelliteDatagramArgument) mSendSatelliteDatagramRequest.argument; + argument.skipCheckingSatelliteAligned = true; + Message message = obtainMessage( + EVENT_SEND_SATELLITE_DATAGRAM_DONE, mSendSatelliteDatagramRequest); + mSendSatelliteDatagramRequest = null; + AsyncResult.forMessage(message, null, null); + message.sendToTarget(); + } + } + } + + private void handleEventSatelliteAlignedTimeout( + @NonNull DatagramDispatcherHandlerRequest request) { + SatelliteManager.SatelliteException exception = + new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_NOT_REACHABLE); + Message message = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request); + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + } + + private boolean isSatelliteAlignedTimerStarted() { + return hasMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT); + } + + private void stopSatelliteAlignedTimer() { + removeMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT); + } + + /** + * Send pending satellite datagrams. Emergency datagrams are given priority over + * non-emergency datagrams. + */ + @GuardedBy("mLock") + private void sendPendingDatagrams() { + logd("sendPendingDatagrams()"); + if (!mDatagramController.isPollingInIdleState()) { + // Datagram should be sent to satellite modem when modem is free. + logd("sendPendingDatagrams: modem is receiving datagrams"); + return; + } + + if (getPendingDatagramCount() <= 0) { + logd("sendPendingDatagrams: no pending datagrams to send"); + return; + } + + Phone phone = SatelliteServiceUtils.getPhone(); + Set> pendingDatagram = null; + if (!mSendingDatagramInProgress && !mPendingEmergencyDatagramsMap.isEmpty()) { + pendingDatagram = mPendingEmergencyDatagramsMap.entrySet(); + } else if (!mSendingDatagramInProgress && !mPendingNonEmergencyDatagramsMap.isEmpty()) { + pendingDatagram = mPendingNonEmergencyDatagramsMap.entrySet(); + } + + if ((pendingDatagram != null) && pendingDatagram.iterator().hasNext()) { + mSendingDatagramInProgress = true; + SendSatelliteDatagramArgument datagramArg = + pendingDatagram.iterator().next().getValue(); + // Sets the trigger time for getting pending datagrams + datagramArg.setDatagramStartTime(); + mDatagramController.updateSendStatus(datagramArg.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, + getPendingDatagramCount(), SatelliteManager.SATELLITE_ERROR_NONE); + sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArg, phone); + } + } + + /** + * Send error code to all the pending datagrams + * + * @param pendingDatagramsMap The pending datagrams map to be cleaned up. + * @param errorCode error code to be returned. + */ + @GuardedBy("mLock") + private void sendErrorCodeAndCleanupPendingDatagrams( + LinkedHashMap pendingDatagramsMap, + @SatelliteManager.SatelliteError int errorCode) { + if (pendingDatagramsMap.size() == 0) { + return; + } + loge("sendErrorCodeAndCleanupPendingDatagrams: cleaning up resources"); + + // Send error code to all the pending datagrams + for (Entry entry : + pendingDatagramsMap.entrySet()) { + SendSatelliteDatagramArgument argument = entry.getValue(); + reportSendDatagramCompleted(argument, errorCode); + mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType); + argument.callback.accept(errorCode); + } + + // Clear pending datagram maps + pendingDatagramsMap.clear(); + } + + /** + * Abort sending all the pending datagrams. + * + * @param subId The subId of the subscription used to send datagram + * @param errorCode The error code that resulted in abort. + */ + @GuardedBy("mLock") + private void abortSendingPendingDatagrams(int subId, + @SatelliteManager.SatelliteError int errorCode) { + logd("abortSendingPendingDatagrams()"); + sendErrorCodeAndCleanupPendingDatagrams(mPendingEmergencyDatagramsMap, errorCode); + sendErrorCodeAndCleanupPendingDatagrams(mPendingNonEmergencyDatagramsMap, errorCode); + } + + /** + * Return pending datagram count + * @return pending datagram count + */ + @GuardedBy("mLock") + private int getPendingDatagramCount() { + return mPendingEmergencyDatagramsMap.size() + mPendingNonEmergencyDatagramsMap.size(); + } + + /** + * Posts the specified command to be executed on the main thread and returns immediately. + * + * @param command command to be executed on the main thread + * @param argument additional parameters required to perform of the operation + * @param phone phone object used to perform the operation. + */ + private void sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone) { + DatagramDispatcherHandlerRequest request = new DatagramDispatcherHandlerRequest( + argument, phone); + Message msg = this.obtainMessage(command, request); + msg.sendToTarget(); + } + + private void reportSendDatagramCompleted(@NonNull SendSatelliteDatagramArgument argument, + @NonNull @SatelliteManager.SatelliteError int resultCode) { + SatelliteStats.getInstance().onSatelliteOutgoingDatagramMetrics( + new SatelliteStats.SatelliteOutgoingDatagramParams.Builder() + .setDatagramType(argument.datagramType) + .setResultCode(resultCode) + .setDatagramSizeBytes(argument.getDatagramRoundedSizeBytes()) + .setDatagramTransferTimeMillis( + System.currentTimeMillis() - argument.datagramStartTime) + .build()); + } + + /** + * Destroys this DatagramDispatcher. Used for tearing down static resources during testing. + */ + @VisibleForTesting + public void destroy() { + sInstance = null; + } + + /** + * This function is used by {@link DatagramController} to notify {@link DatagramDispatcher} + * that satellite modem state has changed. + * + * @param state Current satellite modem state. + */ + public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) { + synchronized (mLock) { + if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF + || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) { + logd("onSatelliteModemStateChanged: cleaning up resources"); + cleanUpResources(); + } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_IDLE) { + sendPendingDatagrams(); + } + } + } + + @GuardedBy("mLock") + private void cleanUpResources() { + mSendingDatagramInProgress = false; + if (getPendingDatagramCount() > 0) { + mDatagramController.updateSendStatus( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED, + getPendingDatagramCount(), SatelliteManager.SATELLITE_REQUEST_ABORTED); + } + mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + 0, SatelliteManager.SATELLITE_ERROR_NONE); + abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + SatelliteManager.SATELLITE_REQUEST_ABORTED); + + stopSatelliteAlignedTimer(); + mIsDemoMode = false; + mSendSatelliteDatagramRequest = null; + mIsAligned = false; + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } + + private static void logw(@NonNull String log) { Rlog.w(TAG, log); } +} diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..06eede122a01b57796b32548d71455e7a6df5f32 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java @@ -0,0 +1,805 @@ +/* + * 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 com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.SQLException; +import android.net.Uri; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.provider.Telephony; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.telephony.satellite.ISatelliteDatagramCallback; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; +import android.util.Pair; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.IIntegerConsumer; +import com.android.internal.telephony.IVoidConsumer; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.metrics.SatelliteStats; +import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; +import com.android.internal.util.FunctionalUtils; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +/** + * Datagram receiver used to receive satellite datagrams and then, + * deliver received datagrams to messaging apps. + */ +public class DatagramReceiver extends Handler { + private static final String TAG = "DatagramReceiver"; + + private static final int CMD_POLL_PENDING_SATELLITE_DATAGRAMS = 1; + private static final int EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE = 2; + private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3; + + /** Key used to read/write satellite datagramId in shared preferences. */ + private static final String SATELLITE_DATAGRAM_ID_KEY = "satellite_datagram_id_key"; + private static AtomicLong mNextDatagramId = new AtomicLong(0); + + @NonNull private static DatagramReceiver sInstance; + @NonNull private final Context mContext; + @NonNull private final ContentResolver mContentResolver; + @NonNull private SharedPreferences mSharedPreferences = null; + @NonNull private final DatagramController mDatagramController; + @NonNull private final ControllerMetricsStats mControllerMetricsStats; + @NonNull private final Looper mLooper; + + private long mDatagramTransferStartTime = 0; + private boolean mIsDemoMode = false; + @GuardedBy("mLock") + private boolean mIsAligned = false; + private DatagramReceiverHandlerRequest mPollPendingSatelliteDatagramsRequest = null; + private final Object mLock = new Object(); + + /** + * Map key: subId, value: SatelliteDatagramListenerHandler to notify registrants. + */ + private final ConcurrentHashMap + mSatelliteDatagramListenerHandlers = new ConcurrentHashMap<>(); + + /** + * Map key: DatagramId, value: pendingAckCount + * This map is used to track number of listeners that are yet to send ack for a particular + * datagram. + */ + private final ConcurrentHashMap + mPendingAckCountHashMap = new ConcurrentHashMap<>(); + + /** + * Create the DatagramReceiver singleton instance. + * @param context The Context to use to create the DatagramReceiver. + * @param looper The looper for the handler. + * @param datagramController DatagramController which is used to update datagram transfer state. + * @return The singleton instance of DatagramReceiver. + */ + public static DatagramReceiver make(@NonNull Context context, @NonNull Looper looper, + @NonNull DatagramController datagramController) { + if (sInstance == null) { + sInstance = new DatagramReceiver(context, looper, datagramController); + } + return sInstance; + } + + /** + * Create a DatagramReceiver to received satellite datagrams. + * The received datagrams will be delivered to respective messaging apps. + * + * @param context The Context for the DatagramReceiver. + * @param looper The looper for the handler. + * @param datagramController DatagramController which is used to update datagram transfer state. + */ + @VisibleForTesting + protected DatagramReceiver(@NonNull Context context, @NonNull Looper looper, + @NonNull DatagramController datagramController) { + super(looper); + mContext = context; + mLooper = looper; + mContentResolver = context.getContentResolver(); + mDatagramController = datagramController; + mControllerMetricsStats = ControllerMetricsStats.getInstance(); + + try { + mSharedPreferences = + mContext.getSharedPreferences(SatelliteController.SATELLITE_SHARED_PREF, + Context.MODE_PRIVATE); + } catch (Exception e) { + loge("Cannot get default shared preferences: " + e); + } + + if ((mSharedPreferences != null) && + (!mSharedPreferences.contains(SATELLITE_DATAGRAM_ID_KEY))) { + mSharedPreferences.edit().putLong(SATELLITE_DATAGRAM_ID_KEY, mNextDatagramId.get()) + .commit(); + } + } + + private static final class DatagramReceiverHandlerRequest { + /** The argument to use for the request */ + public @NonNull Object argument; + /** The caller needs to specify the phone to be used for the request */ + public @NonNull Phone phone; + /** The subId of the subscription used for the request. */ + public int subId; + /** The result of the request that is run on the main thread */ + public @Nullable Object result; + + DatagramReceiverHandlerRequest(Object argument, Phone phone, int subId) { + this.argument = argument; + this.phone = phone; + this.subId = subId; + } + } + + /** + * Listeners are updated about incoming datagrams using a backgroundThread. + */ + @VisibleForTesting + public static final class SatelliteDatagramListenerHandler extends Handler { + public static final int EVENT_SATELLITE_DATAGRAM_RECEIVED = 1; + public static final int EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM = 2; + public static final int EVENT_RECEIVED_ACK = 3; + + @NonNull private final ConcurrentHashMap mListeners; + private final int mSubId; + + private static final class DatagramRetryArgument { + public long datagramId; + @NonNull public SatelliteDatagram datagram; + public int pendingCount; + @NonNull public ISatelliteDatagramCallback listener; + + DatagramRetryArgument(long datagramId, @NonNull SatelliteDatagram datagram, + int pendingCount, @NonNull ISatelliteDatagramCallback listener) { + this.datagramId = datagramId; + this.datagram = datagram; + this.pendingCount = pendingCount; + this.listener = listener; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + DatagramRetryArgument that = (DatagramRetryArgument) other; + return datagramId == that.datagramId + && datagram.equals(that.datagram) + && pendingCount == that.pendingCount + && listener.equals(that.listener); + } + } + + @VisibleForTesting + public SatelliteDatagramListenerHandler(@NonNull Looper looper, int subId) { + super(looper); + mSubId = subId; + mListeners = new ConcurrentHashMap<>(); + } + + public void addListener(@NonNull ISatelliteDatagramCallback listener) { + mListeners.put(listener.asBinder(), listener); + } + + public void removeListener(@NonNull ISatelliteDatagramCallback listener) { + mListeners.remove(listener.asBinder()); + } + + public boolean hasListeners() { + return !mListeners.isEmpty(); + } + + public int getNumOfListeners() { + return mListeners.size(); + } + + private int getTimeoutToReceiveAck() { + return sInstance.mContext.getResources().getInteger( + R.integer.config_timeout_to_receive_delivered_ack_millis); + } + + private long getDatagramId() { + long datagramId = 0; + if (sInstance.mSharedPreferences == null) { + try { + // Try to recreate if it is null + sInstance.mSharedPreferences = sInstance.mContext + .getSharedPreferences(SatelliteController.SATELLITE_SHARED_PREF, + Context.MODE_PRIVATE); + } catch (Exception e) { + loge("Cannot get default shared preferences: " + e); + } + } + + if (sInstance.mSharedPreferences != null) { + long prevDatagramId = sInstance.mSharedPreferences + .getLong(SATELLITE_DATAGRAM_ID_KEY, mNextDatagramId.get()); + datagramId = (prevDatagramId + 1) % DatagramController.MAX_DATAGRAM_ID; + mNextDatagramId.set(datagramId); + sInstance.mSharedPreferences.edit().putLong(SATELLITE_DATAGRAM_ID_KEY, datagramId) + .commit(); + } else { + loge("Shared preferences is null - returning default datagramId"); + datagramId = mNextDatagramId.getAndUpdate( + n -> ((n + 1) % DatagramController.MAX_DATAGRAM_ID)); + } + + return datagramId; + } + + private void insertDatagram(long datagramId, @NonNull SatelliteDatagram datagram) { + ContentValues contentValues = new ContentValues(); + contentValues.put( + Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID, datagramId); + contentValues.put( + Telephony.SatelliteDatagrams.COLUMN_DATAGRAM, datagram.getSatelliteDatagram()); + Uri uri = sInstance.mContentResolver.insert(Telephony.SatelliteDatagrams.CONTENT_URI, + contentValues); + if (uri == null) { + loge("Cannot insert datagram with datagramId: " + datagramId); + } else { + logd("Inserted datagram with datagramId: " + datagramId); + } + } + + private void deleteDatagram(long datagramId) { + String whereClause = (Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID + + "=" + datagramId); + try (Cursor cursor = sInstance.mContentResolver.query( + Telephony.SatelliteDatagrams.CONTENT_URI, + null, whereClause, null, null)) { + if ((cursor != null) && (cursor.getCount() == 1)) { + int numRowsDeleted = sInstance.mContentResolver.delete( + Telephony.SatelliteDatagrams.CONTENT_URI, whereClause, null); + if (numRowsDeleted == 0) { + loge("Cannot delete datagram with datagramId: " + datagramId); + } else { + logd("Deleted datagram with datagramId: " + datagramId); + } + } else { + loge("Datagram with datagramId: " + datagramId + " is not present in DB."); + } + } catch(SQLException e) { + loge("deleteDatagram SQLException e:" + e); + } + } + + private void onSatelliteDatagramReceived(@NonNull DatagramRetryArgument argument) { + try { + IVoidConsumer internalAck = new IVoidConsumer.Stub() { + /** + * This callback will be used by datagram receiver app + * to send ack back to Telephony. If the callback is not + * received within five minutes, then Telephony will + * resend the datagram again. + */ + @Override + public void accept() { + logd("acknowledgeSatelliteDatagramReceived: " + + "datagramId=" + argument.datagramId); + sendMessage(obtainMessage(EVENT_RECEIVED_ACK, argument)); + } + }; + + argument.listener.onSatelliteDatagramReceived(argument.datagramId, + argument.datagram, argument.pendingCount, internalAck); + } catch (RemoteException e) { + logd("EVENT_SATELLITE_DATAGRAM_RECEIVED RemoteException: " + e); + } + } + + @Override + public void handleMessage(@NonNull Message msg) { + switch (msg.what) { + case EVENT_SATELLITE_DATAGRAM_RECEIVED: { + AsyncResult ar = (AsyncResult) msg.obj; + Pair result = + (Pair) ar.result; + SatelliteDatagram satelliteDatagram = result.first; + int pendingCount = result.second; + logd("Received EVENT_SATELLITE_DATAGRAM_RECEIVED for subId=" + mSubId + + " pendingCount:" + pendingCount); + + if (pendingCount <= 0 && satelliteDatagram == null) { + sInstance.mDatagramController.updateReceiveStatus(mSubId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE, + pendingCount, SatelliteManager.SATELLITE_ERROR_NONE); + } else if (satelliteDatagram != null) { + sInstance.mDatagramController.updateReceiveStatus(mSubId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, + pendingCount, SatelliteManager.SATELLITE_ERROR_NONE); + + long datagramId = getDatagramId(); + sInstance.mPendingAckCountHashMap.put(datagramId, getNumOfListeners()); + insertDatagram(datagramId, satelliteDatagram); + + mListeners.values().forEach(listener -> { + DatagramRetryArgument argument = new DatagramRetryArgument(datagramId, + satelliteDatagram, pendingCount, listener); + onSatelliteDatagramReceived(argument); + // wait for ack and retry after the timeout specified. + sendMessageDelayed( + obtainMessage(EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM, + argument), getTimeoutToReceiveAck()); + }); + + sInstance.mControllerMetricsStats.reportIncomingDatagramCount( + SatelliteManager.SATELLITE_ERROR_NONE); + } + + if (pendingCount <= 0) { + sInstance.mDatagramController.updateReceiveStatus(mSubId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + pendingCount, SatelliteManager.SATELLITE_ERROR_NONE); + } else { + // Poll for pending datagrams + IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + logd("pollPendingSatelliteDatagram result: " + result); + } + }; + Consumer callback = FunctionalUtils.ignoreRemoteException( + internalCallback::accept); + sInstance.pollPendingSatelliteDatagramsInternal(mSubId, callback); + } + + // Send the captured data about incoming datagram to metric + sInstance.reportMetrics( + satelliteDatagram, SatelliteManager.SATELLITE_ERROR_NONE); + break; + } + + case EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM: { + DatagramRetryArgument argument = (DatagramRetryArgument) msg.obj; + logd("Received EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM datagramId:" + + argument.datagramId); + onSatelliteDatagramReceived(argument); + break; + } + + case EVENT_RECEIVED_ACK: { + DatagramRetryArgument argument = (DatagramRetryArgument) msg.obj; + int pendingAckCount = sInstance.mPendingAckCountHashMap + .get(argument.datagramId); + pendingAckCount -= 1; + sInstance.mPendingAckCountHashMap.put(argument.datagramId, pendingAckCount); + logd("Received EVENT_RECEIVED_ACK datagramId:" + argument.datagramId); + removeMessages(EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM, argument); + + if (pendingAckCount <= 0) { + // Delete datagram from DB after receiving ack from all listeners + deleteDatagram(argument.datagramId); + sInstance.mPendingAckCountHashMap.remove(argument.datagramId); + } + break; + } + + default: + loge("SatelliteDatagramListenerHandler unknown event: " + msg.what); + } + } + } + + @Override + public void handleMessage(Message msg) { + DatagramReceiverHandlerRequest request; + Message onCompleted; + AsyncResult ar; + + switch (msg.what) { + case CMD_POLL_PENDING_SATELLITE_DATAGRAMS: { + request = (DatagramReceiverHandlerRequest) msg.obj; + onCompleted = + obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request); + + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance() + .pollPendingSatelliteDatagrams(onCompleted); + break; + } + + Phone phone = request.phone; + if (phone != null) { + phone.pollPendingSatelliteDatagrams(onCompleted); + } else { + loge("pollPendingSatelliteDatagrams: No phone object"); + mDatagramController.updateReceiveStatus(request.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED, + mDatagramController.getReceivePendingCount(), + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + + mDatagramController.updateReceiveStatus(request.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + mDatagramController.getReceivePendingCount(), + SatelliteManager.SATELLITE_ERROR_NONE); + + reportMetrics(null, SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + mControllerMetricsStats.reportIncomingDatagramCount( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + // Send response for current request + ((Consumer) request.argument) + .accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + break; + } + + case EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE: { + ar = (AsyncResult) msg.obj; + request = (DatagramReceiverHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, + "pollPendingSatelliteDatagrams"); + + if (mIsDemoMode && error == SatelliteManager.SATELLITE_ERROR_NONE) { + SatelliteDatagram datagram = mDatagramController.getDemoModeDatagram(); + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId( + request.subId, mContext); + SatelliteDatagramListenerHandler listenerHandler = + mSatelliteDatagramListenerHandlers.get(validSubId); + if (listenerHandler != null) { + Pair pair = new Pair<>(datagram, 0); + ar = new AsyncResult(null, pair, null); + Message message = listenerHandler.obtainMessage( + SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, + ar); + listenerHandler.sendMessage(message); + } else { + error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } + } + + logd("EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE error: " + error); + if (error != SatelliteManager.SATELLITE_ERROR_NONE) { + mDatagramController.updateReceiveStatus(request.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED, + mDatagramController.getReceivePendingCount(), error); + + mDatagramController.updateReceiveStatus(request.subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + mDatagramController.getReceivePendingCount(), + SatelliteManager.SATELLITE_ERROR_NONE); + + reportMetrics(null, error); + mControllerMetricsStats.reportIncomingDatagramCount(error); + } + // Send response for current request + ((Consumer) request.argument).accept(error); + break; + } + + case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: { + handleEventSatelliteAlignedTimeout((DatagramReceiverHandlerRequest) msg.obj); + break; + } + } + } + + /** + * Register to receive incoming datagrams over satellite. + * + * @param subId The subId of the subscription to register for incoming satellite datagrams. + * @param callback The callback to handle incoming datagrams over satellite. + * + * @return The {@link SatelliteManager.SatelliteError} result of the operation. + */ + @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId, + @NonNull ISatelliteDatagramCallback callback) { + if (!SatelliteController.getInstance().isSatelliteSupported()) { + return SatelliteManager.SATELLITE_NOT_SUPPORTED; + } + + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + SatelliteDatagramListenerHandler satelliteDatagramListenerHandler = + mSatelliteDatagramListenerHandlers.get(validSubId); + if (satelliteDatagramListenerHandler == null) { + satelliteDatagramListenerHandler = new SatelliteDatagramListenerHandler( + mLooper, validSubId); + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance().registerForSatelliteDatagramsReceived( + satelliteDatagramListenerHandler, + SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null); + } else { + Phone phone = SatelliteServiceUtils.getPhone(); + phone.registerForSatelliteDatagramsReceived(satelliteDatagramListenerHandler, + SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null); + } + } + + satelliteDatagramListenerHandler.addListener(callback); + mSatelliteDatagramListenerHandlers.put(validSubId, satelliteDatagramListenerHandler); + return SatelliteManager.SATELLITE_ERROR_NONE; + } + + /** + * Unregister to stop receiving incoming datagrams over satellite. + * If callback was not registered before, the request will be ignored. + * + * @param subId The subId of the subscription to unregister for incoming satellite datagrams. + * @param callback The callback that was passed to + * {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}. + */ + public void unregisterForSatelliteDatagram(int subId, + @NonNull ISatelliteDatagramCallback callback) { + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + SatelliteDatagramListenerHandler handler = + mSatelliteDatagramListenerHandlers.get(validSubId); + if (handler != null) { + handler.removeListener(callback); + + if (!handler.hasListeners()) { + mSatelliteDatagramListenerHandlers.remove(validSubId); + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance() + .unregisterForSatelliteDatagramsReceived(handler); + } else { + Phone phone = SatelliteServiceUtils.getPhone(); + if (phone != null) { + phone.unregisterForSatelliteDatagramsReceived(handler); + } + } + } + } + } + + /** + * Poll pending satellite datagrams over satellite. + * + * This method requests modem to check if there are any pending datagrams to be received over + * satellite. If there are any incoming datagrams, they will be received via + * {@link android.telephony.satellite.SatelliteDatagramCallback + * #onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)} + * + * @param subId The subId of the subscription used for receiving datagrams. + * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request. + */ + public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer callback) { + if (!mDatagramController.isPollingInIdleState()) { + // Poll request should be sent to satellite modem only when it is free. + logd("pollPendingSatelliteDatagrams: satellite modem is busy receiving datagrams."); + callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY); + return; + } + + pollPendingSatelliteDatagramsInternal(subId, callback); + } + + private void pollPendingSatelliteDatagramsInternal(int subId, + @NonNull Consumer callback) { + if (!mDatagramController.isSendingInIdleState()) { + // Poll request should be sent to satellite modem only when it is free. + logd("pollPendingSatelliteDatagrams: satellite modem is busy sending datagrams."); + callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY); + return; + } + + mDatagramController.updateReceiveStatus(subId, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING, + mDatagramController.getReceivePendingCount(), + SatelliteManager.SATELLITE_ERROR_NONE); + mDatagramTransferStartTime = System.currentTimeMillis(); + Phone phone = SatelliteServiceUtils.getPhone(); + + if (mIsDemoMode) { + DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest( + callback, phone, subId); + synchronized (mLock) { + if (mIsAligned) { + Message msg = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, + request); + AsyncResult.forMessage(msg, null, null); + msg.sendToTarget(); + } else { + startSatelliteAlignedTimer(request); + } + } + } else { + sendRequestAsync(CMD_POLL_PENDING_SATELLITE_DATAGRAMS, callback, phone, subId); + } + } + + /** + * This function is used by {@link DatagramController} to notify {@link DatagramReceiver} + * that satellite modem state has changed. + * + * @param state Current satellite modem state. + */ + public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) { + synchronized (mLock) { + if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF + || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) { + logd("onSatelliteModemStateChanged: cleaning up resources"); + cleanUpResources(); + } + } + } + + @GuardedBy("mLock") + private void cleanupDemoModeResources() { + if (isSatelliteAlignedTimerStarted()) { + stopSatelliteAlignedTimer(); + if (mPollPendingSatelliteDatagramsRequest == null) { + loge("Satellite aligned timer was started " + + "but mPollPendingSatelliteDatagramsRequest is null"); + } else { + Consumer callback = + (Consumer) mPollPendingSatelliteDatagramsRequest.argument; + callback.accept(SatelliteManager.SATELLITE_REQUEST_ABORTED); + } + } + mIsDemoMode = false; + mPollPendingSatelliteDatagramsRequest = null; + mIsAligned = false; + } + + @GuardedBy("mLock") + private void cleanUpResources() { + if (mDatagramController.isReceivingDatagrams()) { + mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED, + mDatagramController.getReceivePendingCount(), + SatelliteManager.SATELLITE_REQUEST_ABORTED); + } + mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0, + SatelliteManager.SATELLITE_ERROR_NONE); + cleanupDemoModeResources(); + } + + /** + * Posts the specified command to be executed on the main thread and returns immediately. + * + * @param command command to be executed on the main thread + * @param argument additional parameters required to perform of the operation + * @param phone phone object used to perform the operation + * @param subId The subId of the subscription used for the request. + */ + private void sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone, + int subId) { + DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest( + argument, phone, subId); + Message msg = this.obtainMessage(command, request); + msg.sendToTarget(); + } + + /** Report incoming datagram related metrics */ + private void reportMetrics(@Nullable SatelliteDatagram satelliteDatagram, + @NonNull @SatelliteManager.SatelliteError int resultCode) { + int datagramSizeRoundedBytes = -1; + int datagramTransferTime = 0; + + if (satelliteDatagram != null) { + if (satelliteDatagram.getSatelliteDatagram() != null) { + int sizeBytes = satelliteDatagram.getSatelliteDatagram().length; + // rounded by 10 bytes + datagramSizeRoundedBytes = + (int) (Math.round((double) sizeBytes / ROUNDING_UNIT) * ROUNDING_UNIT); + } + datagramTransferTime = (int) (System.currentTimeMillis() - mDatagramTransferStartTime); + mDatagramTransferStartTime = 0; + } + + SatelliteStats.getInstance().onSatelliteIncomingDatagramMetrics( + new SatelliteStats.SatelliteIncomingDatagramParams.Builder() + .setResultCode(resultCode) + .setDatagramSizeBytes(datagramSizeRoundedBytes) + .setDatagramTransferTimeMillis(datagramTransferTime) + .build()); + } + + /** Set demo mode + * + * @param isDemoMode {@code true} means demo mode is on, {@code false} otherwise. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected void setDemoMode(boolean isDemoMode) { + mIsDemoMode = isDemoMode; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected void onDeviceAlignedWithSatellite(boolean isAligned) { + if (mIsDemoMode) { + synchronized (mLock) { + mIsAligned = isAligned; + if (isAligned) handleEventSatelliteAligned(); + } + } + } + + private void startSatelliteAlignedTimer(DatagramReceiverHandlerRequest request) { + if (isSatelliteAlignedTimerStarted()) { + logd("Satellite aligned timer was already started"); + return; + } + mPollPendingSatelliteDatagramsRequest = request; + sendMessageDelayed( + obtainMessage(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT, request), + getSatelliteAlignedTimeoutDuration()); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected long getSatelliteAlignedTimeoutDuration() { + return mDatagramController.getSatelliteAlignedTimeoutDuration(); + } + + private void handleEventSatelliteAligned() { + if (isSatelliteAlignedTimerStarted()) { + stopSatelliteAlignedTimer(); + + if (mPollPendingSatelliteDatagramsRequest == null) { + loge("handleSatelliteAlignedTimer: mPollPendingSatelliteDatagramsRequest is null"); + } else { + Message message = obtainMessage( + EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, + mPollPendingSatelliteDatagramsRequest); + mPollPendingSatelliteDatagramsRequest = null; + AsyncResult.forMessage(message, null, null); + message.sendToTarget(); + } + } + } + + private void handleEventSatelliteAlignedTimeout(DatagramReceiverHandlerRequest request) { + SatelliteManager.SatelliteException exception = + new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_NOT_REACHABLE); + Message message = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request); + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + } + + private boolean isSatelliteAlignedTimerStarted() { + return hasMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT); + } + + private void stopSatelliteAlignedTimer() { + removeMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT); + } + + /** + * Destroys this DatagramDispatcher. Used for tearing down static resources during testing. + */ + @VisibleForTesting + public void destroy() { + sInstance = null; + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java new file mode 100644 index 0000000000000000000000000000000000000000..f7f93cf4415943c51e97d3e5a21b8f4264c70703 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java @@ -0,0 +1,476 @@ +/* + * 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.annotation.Nullable; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncResult; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.telephony.Rlog; +import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; +import android.telephony.satellite.PointingInfo; +import android.telephony.satellite.SatelliteManager; +import android.text.TextUtils; + +import com.android.internal.R; +import com.android.internal.telephony.Phone; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * PointingApp controller to manage interactions with PointingUI app. + */ +public class PointingAppController { + private static final String TAG = "PointingAppController"; + private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; + private static final boolean DEBUG = !"user".equals(Build.TYPE); + + @NonNull + private static PointingAppController sInstance; + @NonNull private final Context mContext; + private boolean mStartedSatelliteTransmissionUpdates; + @NonNull private String mPointingUiPackageName = ""; + @NonNull private String mPointingUiClassName = ""; + + /** + * Map key: subId, value: SatelliteTransmissionUpdateHandler to notify registrants. + */ + private final ConcurrentHashMap + mSatelliteTransmissionUpdateHandlers = new ConcurrentHashMap<>(); + + /** + * @return The singleton instance of PointingAppController. + */ + public static PointingAppController getInstance() { + if (sInstance == null) { + loge("PointingAppController was not yet initialized."); + } + return sInstance; + } + + /** + * Create the PointingAppController singleton instance. + * @param context The Context to use to create the PointingAppController. + * @return The singleton instance of PointingAppController. + */ + public static PointingAppController make(@NonNull Context context) { + if (sInstance == null) { + sInstance = new PointingAppController(context); + } + return sInstance; + } + + /** + * Create a PointingAppController to manage interactions with PointingUI app. + * + * @param context The Context for the PointingUIController. + */ + private PointingAppController(@NonNull Context context) { + mContext = context; + mStartedSatelliteTransmissionUpdates = false; + } + + /** + * Set the flag mStartedSatelliteTransmissionUpdates to true or false based on the state of + * transmission updates + * @param startedSatelliteTransmissionUpdates boolean to set the flag + */ + public void setStartedSatelliteTransmissionUpdates( + boolean startedSatelliteTransmissionUpdates) { + mStartedSatelliteTransmissionUpdates = startedSatelliteTransmissionUpdates; + } + + private static final class DatagramTransferStateHandlerRequest { + public int datagramTransferState; + public int pendingCount; + public int errorCode; + + DatagramTransferStateHandlerRequest(int datagramTransferState, int pendingCount, + int errorCode) { + this.datagramTransferState = datagramTransferState; + this.pendingCount = pendingCount; + this.errorCode = errorCode; + } + } + + + private static final class SatelliteTransmissionUpdateHandler extends Handler { + public static final int EVENT_POSITION_INFO_CHANGED = 1; + public static final int EVENT_SEND_DATAGRAM_STATE_CHANGED = 2; + public static final int EVENT_RECEIVE_DATAGRAM_STATE_CHANGED = 3; + public static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 4; + + private final ConcurrentHashMap mListeners; + SatelliteTransmissionUpdateHandler(Looper looper) { + super(looper); + mListeners = new ConcurrentHashMap<>(); + } + + public void addListener(ISatelliteTransmissionUpdateCallback listener) { + mListeners.put(listener.asBinder(), listener); + } + + public void removeListener(ISatelliteTransmissionUpdateCallback listener) { + mListeners.remove(listener.asBinder()); + } + + public boolean hasListeners() { + return !mListeners.isEmpty(); + } + + @Override + public void handleMessage(@NonNull Message msg) { + switch (msg.what) { + case EVENT_POSITION_INFO_CHANGED: { + AsyncResult ar = (AsyncResult) msg.obj; + PointingInfo pointingInfo = (PointingInfo) ar.result; + List toBeRemoved = new ArrayList<>(); + mListeners.values().forEach(listener -> { + try { + listener.onSatellitePositionChanged(pointingInfo); + } catch (RemoteException e) { + logd("EVENT_POSITION_INFO_CHANGED RemoteException: " + e); + toBeRemoved.add(listener.asBinder()); + } + }); + toBeRemoved.forEach(listener -> { + mListeners.remove(listener); + }); + break; + } + + case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED: { + AsyncResult ar = (AsyncResult) msg.obj; + logd("Receive EVENT_DATAGRAM_TRANSFER_STATE_CHANGED state=" + (int) ar.result); + break; + } + + case EVENT_SEND_DATAGRAM_STATE_CHANGED: { + logd("Received EVENT_SEND_DATAGRAM_STATE_CHANGED"); + DatagramTransferStateHandlerRequest request = + (DatagramTransferStateHandlerRequest) msg.obj; + List toBeRemoved = new ArrayList<>(); + mListeners.values().forEach(listener -> { + try { + listener.onSendDatagramStateChanged(request.datagramTransferState, + request.pendingCount, request.errorCode); + } catch (RemoteException e) { + logd("EVENT_SEND_DATAGRAM_STATE_CHANGED RemoteException: " + e); + toBeRemoved.add(listener.asBinder()); + } + }); + toBeRemoved.forEach(listener -> { + mListeners.remove(listener); + }); + break; + } + + case EVENT_RECEIVE_DATAGRAM_STATE_CHANGED: { + logd("Received EVENT_RECEIVE_DATAGRAM_STATE_CHANGED"); + DatagramTransferStateHandlerRequest request = + (DatagramTransferStateHandlerRequest) msg.obj; + List toBeRemoved = new ArrayList<>(); + mListeners.values().forEach(listener -> { + try { + listener.onReceiveDatagramStateChanged(request.datagramTransferState, + request.pendingCount, request.errorCode); + } catch (RemoteException e) { + logd("EVENT_RECEIVE_DATAGRAM_STATE_CHANGED RemoteException: " + e); + toBeRemoved.add(listener.asBinder()); + } + }); + toBeRemoved.forEach(listener -> { + mListeners.remove(listener); + }); + break; + } + + default: + loge("SatelliteTransmissionUpdateHandler unknown event: " + msg.what); + } + } + } + + /** + * Register to start receiving updates for satellite position and datagram transfer state + * @param subId The subId of the subscription to register for receiving the updates. + * @param callback The callback to notify of satellite transmission updates. + * @param phone The Phone object to unregister for receiving the updates. + */ + public void registerForSatelliteTransmissionUpdates(int subId, + ISatelliteTransmissionUpdateCallback callback, Phone phone) { + SatelliteTransmissionUpdateHandler handler = + mSatelliteTransmissionUpdateHandlers.get(subId); + if (handler != null) { + handler.addListener(callback); + return; + } else { + handler = new SatelliteTransmissionUpdateHandler(Looper.getMainLooper()); + handler.addListener(callback); + mSatelliteTransmissionUpdateHandlers.put(subId, handler); + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance().registerForSatellitePositionInfoChanged( + handler, SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED, + null); + SatelliteModemInterface.getInstance().registerForDatagramTransferStateChanged( + handler, + SatelliteTransmissionUpdateHandler.EVENT_DATAGRAM_TRANSFER_STATE_CHANGED, + null); + } else { + phone.registerForSatellitePositionInfoChanged(handler, + SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED, null); + } + } + } + + /** + * Unregister to stop receiving updates on satellite position and datagram transfer state + * If the callback was not registered before, it is ignored + * @param subId The subId of the subscription to unregister for receiving the updates. + * @param result The callback to get the error code in case of failure + * @param callback The callback that was passed to {@link + * #registerForSatelliteTransmissionUpdates(int, ISatelliteTransmissionUpdateCallback, Phone)}. + * @param phone The Phone object to unregister for receiving the updates + */ + public void unregisterForSatelliteTransmissionUpdates(int subId, Consumer result, + ISatelliteTransmissionUpdateCallback callback, Phone phone) { + SatelliteTransmissionUpdateHandler handler = + mSatelliteTransmissionUpdateHandlers.get(subId); + if (handler != null) { + handler.removeListener(callback); + + if (handler.hasListeners()) { + result.accept(SatelliteManager.SATELLITE_ERROR_NONE); + return; + } + + mSatelliteTransmissionUpdateHandlers.remove(subId); + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance().unregisterForSatellitePositionInfoChanged( + handler); + SatelliteModemInterface.getInstance().unregisterForDatagramTransferStateChanged( + handler); + } else { + if (phone == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + phone.unregisterForSatellitePositionInfoChanged(handler); + } + } + } + + /** + * Start receiving satellite trasmission updates. + * This can be called by the pointing UI when the user starts pointing to the satellite. + * Modem should continue to report the pointing input as the device or satellite moves. + * The transmission updates will be received via + * {@link android.telephony.satellite.SatelliteTransmissionUpdateCallback + * #onSatellitePositionChanged(pointingInfo)}. + */ + public void startSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) { + if (mStartedSatelliteTransmissionUpdates) { + logd("startSatelliteTransmissionUpdates: already started"); + AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_ERROR_NONE)); + message.sendToTarget(); + return; + } + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance().startSendingSatellitePointingInfo(message); + mStartedSatelliteTransmissionUpdates = true; + return; + } + if (phone != null) { + phone.startSatellitePositionUpdates(message); + mStartedSatelliteTransmissionUpdates = true; + } else { + loge("startSatelliteTransmissionUpdates: No phone object"); + AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)); + message.sendToTarget(); + } + } + + /** + * Stop receiving satellite transmission updates. + * 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) { + if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { + SatelliteModemInterface.getInstance().stopSendingSatellitePointingInfo(message); + return; + } + if (phone != null) { + phone.stopSatellitePositionUpdates(message); + } else { + loge("startSatelliteTransmissionUpdates: No phone object"); + AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)); + message.sendToTarget(); + } + } + + /** + * Check if Pointing is needed and Launch Pointing UI + * @param needFullScreenPointingUI if pointing UI has to be launchd with Full screen + */ + public void startPointingUI(boolean needFullScreenPointingUI) { + String packageName = getPointingUiPackageName(); + if (TextUtils.isEmpty(packageName)) { + logd("startPointingUI: config_pointing_ui_package is not set. Ignore the request"); + return; + } + + Intent launchIntent; + String className = getPointingUiClassName(); + if (!TextUtils.isEmpty(className)) { + launchIntent = new Intent() + .setComponent(new ComponentName(packageName, className)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } else { + launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName); + } + if (launchIntent == null) { + loge("startPointingUI: launchIntent is null"); + return; + } + launchIntent.putExtra("needFullScreen", needFullScreenPointingUI); + + try { + mContext.startActivity(launchIntent); + } catch (ActivityNotFoundException ex) { + loge("startPointingUI: Pointing UI app activity is not found, ex=" + ex); + } + } + + public void updateSendDatagramTransferState(int subId, + @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, + int sendPendingCount, int errorCode) { + DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest( + datagramTransferState, sendPendingCount, errorCode); + SatelliteTransmissionUpdateHandler handler = + mSatelliteTransmissionUpdateHandlers.get(subId); + + if (handler != null) { + Message msg = handler.obtainMessage( + SatelliteTransmissionUpdateHandler.EVENT_SEND_DATAGRAM_STATE_CHANGED, + request); + msg.sendToTarget(); + } else { + loge("SatelliteTransmissionUpdateHandler not found for subId: " + subId); + } + } + + public void updateReceiveDatagramTransferState(int subId, + @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, + int receivePendingCount, int errorCode) { + DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest( + datagramTransferState, receivePendingCount, errorCode); + SatelliteTransmissionUpdateHandler handler = + mSatelliteTransmissionUpdateHandlers.get(subId); + + if (handler != null) { + Message msg = handler.obtainMessage( + SatelliteTransmissionUpdateHandler.EVENT_RECEIVE_DATAGRAM_STATE_CHANGED, + request); + msg.sendToTarget(); + } else { + loge(" SatelliteTransmissionUpdateHandler not found for subId: " + subId); + } + } + + /** + * This API can be used by only CTS to update satellite pointing UI app package and class names. + * + * @param packageName The package name of the satellite pointing UI app. + * @param className The class name of the satellite pointing UI app. + * @return {@code true} if the satellite pointing UI app package and class is set successfully, + * {@code false} otherwise. + */ + boolean setSatellitePointingUiClassName( + @Nullable String packageName, @Nullable String className) { + if (!isMockModemAllowed()) { + loge("setSatellitePointingUiClassName: modifying satellite pointing UI package and " + + "class name is not allowed"); + return false; + } + + logd("setSatellitePointingUiClassName: config_pointing_ui_package is updated, new " + + "packageName=" + packageName + + ", config_pointing_ui_class new className=" + className); + + if (packageName == null || packageName.equals("null")) { + mPointingUiPackageName = ""; + mPointingUiClassName = ""; + } else { + mPointingUiPackageName = packageName; + if (className == null || className.equals("null")) { + mPointingUiClassName = ""; + } else { + mPointingUiClassName = className; + } + } + + return true; + } + + @NonNull private String getPointingUiPackageName() { + if (!TextUtils.isEmpty(mPointingUiPackageName)) { + return mPointingUiPackageName; + } + return TextUtils.emptyIfNull(mContext.getResources().getString( + R.string.config_pointing_ui_package)); + } + + @NonNull private String getPointingUiClassName() { + if (!TextUtils.isEmpty(mPointingUiClassName)) { + return mPointingUiClassName; + } + return TextUtils.emptyIfNull(mContext.getResources().getString( + R.string.config_pointing_ui_class)); + } + + private boolean isMockModemAllowed() { + return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)); + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } + /** + * TODO: The following needs to be added in this class: + * - check if pointingUI crashes - then restart it + */ +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java new file mode 100644 index 0000000000000000000000000000000000000000..5cd84446c704c05221d25fa507a26f3a0ac2879c --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java @@ -0,0 +1,2280 @@ +/* + * 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.annotation.Nullable; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +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.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.provider.Settings; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.telephony.satellite.ISatelliteDatagramCallback; +import android.telephony.satellite.ISatelliteProvisionStateCallback; +import android.telephony.satellite.ISatelliteStateCallback; +import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; +import android.telephony.satellite.SatelliteCapabilities; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; +import android.util.Log; +import android.uwb.UwbManager; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.IIntegerConsumer; +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.util.FunctionalUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +/** + * Satellite controller is the backend service of + * {@link android.telephony.satellite.SatelliteManager}. + */ +public class SatelliteController extends Handler { + private static final String TAG = "SatelliteController"; + /** Whether enabling verbose debugging message or not. */ + private static final boolean DBG = false; + /** 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 */ + public static final int SATELLITE_MODE_ENABLED_TRUE = 1; + public static final int SATELLITE_MODE_ENABLED_FALSE = 0; + + /** Message codes used in handleMessage() */ + //TODO: Move the Commands and events related to position updates to PointingAppController + private static final int CMD_START_SATELLITE_TRANSMISSION_UPDATES = 1; + private static final int EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE = 2; + private static final int CMD_STOP_SATELLITE_TRANSMISSION_UPDATES = 3; + private static final int EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE = 4; + private static final int CMD_PROVISION_SATELLITE_SERVICE = 7; + private static final int EVENT_PROVISION_SATELLITE_SERVICE_DONE = 8; + private static final int CMD_DEPROVISION_SATELLITE_SERVICE = 9; + private static final int EVENT_DEPROVISION_SATELLITE_SERVICE_DONE = 10; + private static final int CMD_SET_SATELLITE_ENABLED = 11; + private static final int EVENT_SET_SATELLITE_ENABLED_DONE = 12; + private static final int CMD_IS_SATELLITE_ENABLED = 13; + private static final int EVENT_IS_SATELLITE_ENABLED_DONE = 14; + private static final int CMD_IS_SATELLITE_SUPPORTED = 15; + private static final int EVENT_IS_SATELLITE_SUPPORTED_DONE = 16; + private static final int CMD_GET_SATELLITE_CAPABILITIES = 17; + private static final int EVENT_GET_SATELLITE_CAPABILITIES_DONE = 18; + private static final int CMD_IS_SATELLITE_COMMUNICATION_ALLOWED = 19; + private static final int EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE = 20; + private static final int CMD_GET_TIME_SATELLITE_NEXT_VISIBLE = 21; + private static final int EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE = 22; + private static final int EVENT_RADIO_STATE_CHANGED = 23; + private static final int CMD_IS_SATELLITE_PROVISIONED = 24; + private static final int EVENT_IS_SATELLITE_PROVISIONED_DONE = 25; + private static final int EVENT_SATELLITE_PROVISION_STATE_CHANGED = 26; + private static final int EVENT_PENDING_DATAGRAMS = 27; + private static final int EVENT_SATELLITE_MODEM_STATE_CHANGED = 28; + + @NonNull private static SatelliteController sInstance; + @NonNull private final Context mContext; + @NonNull private final SatelliteModemInterface mSatelliteModemInterface; + @NonNull private SatelliteSessionController mSatelliteSessionController; + @NonNull private final PointingAppController mPointingAppController; + @NonNull private final DatagramController mDatagramController; + @NonNull private final ControllerMetricsStats mControllerMetricsStats; + @NonNull private final ProvisionMetricsStats mProvisionMetricsStats; + private SharedPreferences mSharedPreferences = null; + private final CommandsInterface mCi; + private ContentResolver mContentResolver = null; + + private final Object mRadioStateLock = new Object(); + + /** Flags to indicate whether the resepective radio is enabled */ + @GuardedBy("mRadioStateLock") + private boolean mBTStateEnabled = false; + @GuardedBy("mRadioStateLock") + private boolean mNfcStateEnabled = false; + @GuardedBy("mRadioStateLock") + private boolean mUwbStateEnabled = false; + @GuardedBy("mRadioStateLock") + private boolean mWifiStateEnabled = false; + + // Flags to indicate that respective radios need to be disabled when satellite is enabled + private boolean mDisableBTOnSatelliteEnabled = false; + private boolean mDisableNFCOnSatelliteEnabled = false; + private boolean mDisableUWBOnSatelliteEnabled = false; + private boolean mDisableWifiOnSatelliteEnabled = false; + + private final Object mSatelliteEnabledRequestLock = new Object(); + @GuardedBy("mSatelliteEnabledRequestLock") + private RequestSatelliteEnabledArgument mSatelliteEnabledRequest = null; + /** Flag to indicate that satellite is enabled successfully + * and waiting for all the radios to be disabled so that success can be sent to callback + */ + @GuardedBy("mSatelliteEnabledRequestLock") + private boolean mWaitingForRadioDisabled = false; + + private final AtomicBoolean mRegisteredForProvisionStateChangedWithSatelliteService = + new AtomicBoolean(false); + private final AtomicBoolean mRegisteredForProvisionStateChangedWithPhone = + new AtomicBoolean(false); + private final AtomicBoolean mRegisteredForPendingDatagramCountWithSatelliteService = + new AtomicBoolean(false); + private final AtomicBoolean mRegisteredForPendingDatagramCountWithPhone = + new AtomicBoolean(false); + private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithSatelliteService = + new AtomicBoolean(false); + private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithPhone = + new AtomicBoolean(false); + /** + * Map key: subId, value: callback to get error code of the provision request. + */ + private final ConcurrentHashMap> mSatelliteProvisionCallbacks = + new ConcurrentHashMap<>(); + + /** + * Map key: binder of the callback, value: callback to receive provision state changed events. + */ + private final ConcurrentHashMap + mSatelliteProvisionStateChangedListeners = new ConcurrentHashMap<>(); + private final Object mIsSatelliteSupportedLock = new Object(); + @GuardedBy("mIsSatelliteSupportedLock") + private Boolean mIsSatelliteSupported = null; + private boolean mIsDemoModeEnabled = false; + private final Object mIsSatelliteEnabledLock = new Object(); + @GuardedBy("mIsSatelliteEnabledLock") + private Boolean mIsSatelliteEnabled = null; + private boolean mIsRadioOn = false; + private final Object mIsSatelliteProvisionedLock = new Object(); + @GuardedBy("mIsSatelliteProvisionedLock") + private Boolean mIsSatelliteProvisioned = null; + private final Object mSatelliteCapabilitiesLock = new Object(); + @GuardedBy("mSatelliteCapabilitiesLock") + private SatelliteCapabilities mSatelliteCapabilities; + private final Object mNeedsSatellitePointingLock = new Object(); + @GuardedBy("mNeedsSatellitePointingLock") + private boolean mNeedsSatellitePointing = false; + + /** + * @return The singleton instance of SatelliteController. + */ + public static SatelliteController getInstance() { + if (sInstance == null) { + loge("SatelliteController was not yet initialized."); + } + return sInstance; + } + + /** + * Create the SatelliteController singleton instance. + * @param context The Context to use to create the SatelliteController. + */ + public static void make(@NonNull Context context) { + if (sInstance == null) { + HandlerThread satelliteThread = new HandlerThread(TAG); + satelliteThread.start(); + sInstance = new SatelliteController(context, satelliteThread.getLooper()); + } + } + + /** + * Create a SatelliteController to act as a backend service of + * {@link android.telephony.satellite.SatelliteManager} + * + * @param context The Context for the SatelliteController. + * @param looper The looper for the handler. It does not run on main thread. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public SatelliteController(@NonNull Context context, @NonNull Looper looper) { + super(looper); + + mContext = context; + Phone phone = SatelliteServiceUtils.getPhone(); + mCi = phone.mCi; + // Create the SatelliteModemInterface singleton, which is used to manage connections + // to the satellite service and HAL interface. + mSatelliteModemInterface = SatelliteModemInterface.make(mContext, this); + + // Create the PointingUIController singleton, + // which is used to manage interactions with PointingUI app. + mPointingAppController = PointingAppController.make(mContext); + + // Create the SatelliteControllerMetrics to report controller metrics + // should be called before making DatagramController + mControllerMetricsStats = ControllerMetricsStats.make(mContext); + mProvisionMetricsStats = ProvisionMetricsStats.getOrCreateInstance(); + + // Create the DatagramController singleton, + // which is used to send and receive satellite datagrams. + mDatagramController = DatagramController.make(mContext, looper, mPointingAppController); + + requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + new ResultReceiver(this) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + logd("requestIsSatelliteSupported: resultCode=" + resultCode); + } + }); + mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null); + mIsRadioOn = phone.isRadioOn(); + registerForSatelliteProvisionStateChanged(); + registerForPendingDatagramCount(); + registerForSatelliteModemStateChanged(); + mContentResolver = mContext.getContentResolver(); + + try { + mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF, + Context.MODE_PRIVATE); + } catch (Exception e) { + loge("Cannot get default shared preferences: " + e); + } + + initializeSatelliteModeRadios(); + + ContentObserver satelliteModeRadiosContentObserver = new ContentObserver(this) { + @Override + public void onChange(boolean selfChange) { + initializeSatelliteModeRadios(); + } + }; + if (mContentResolver != null) { + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.SATELLITE_MODE_RADIOS), + false, satelliteModeRadiosContentObserver); + } + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected void initializeSatelliteModeRadios() { + if (mContentResolver != null) { + BTWifiNFCStateReceiver bTWifiNFCSateReceiver = new BTWifiNFCStateReceiver(); + UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback(); + IntentFilter radioStateIntentFilter = new IntentFilter(); + + synchronized (mRadioStateLock) { + // Initialize radio states to default value + mDisableBTOnSatelliteEnabled = false; + mDisableNFCOnSatelliteEnabled = false; + mDisableWifiOnSatelliteEnabled = false; + mDisableUWBOnSatelliteEnabled = false; + + mBTStateEnabled = false; + mNfcStateEnabled = false; + mWifiStateEnabled = false; + mUwbStateEnabled = false; + + // Read satellite mode radios from settings + String satelliteModeRadios = Settings.Global.getString(mContentResolver, + Settings.Global.SATELLITE_MODE_RADIOS); + if (satelliteModeRadios == null) { + loge("initializeSatelliteModeRadios: satelliteModeRadios is null"); + return; + } + logd("Radios To be checked when satellite is on: " + satelliteModeRadios); + + if (satelliteModeRadios.contains(Settings.Global.RADIO_BLUETOOTH)) { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter != null) { + mDisableBTOnSatelliteEnabled = true; + mBTStateEnabled = bluetoothAdapter.isEnabled(); + radioStateIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + } + } + + if (satelliteModeRadios.contains(Settings.Global.RADIO_NFC)) { + Context applicationContext = mContext.getApplicationContext(); + NfcAdapter nfcAdapter = null; + if (applicationContext != null) { + nfcAdapter = NfcAdapter.getDefaultAdapter(mContext.getApplicationContext()); + } + if (nfcAdapter != null) { + mDisableNFCOnSatelliteEnabled = true; + mNfcStateEnabled = nfcAdapter.isEnabled(); + radioStateIntentFilter.addAction(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); + } + } + + if (satelliteModeRadios.contains(Settings.Global.RADIO_WIFI)) { + WifiManager wifiManager = mContext.getSystemService(WifiManager.class); + if (wifiManager != null) { + mDisableWifiOnSatelliteEnabled = true; + mWifiStateEnabled = wifiManager.isWifiEnabled(); + radioStateIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + } + } + mContext.registerReceiver(bTWifiNFCSateReceiver, radioStateIntentFilter); + + if (satelliteModeRadios.contains(Settings.Global.RADIO_UWB)) { + UwbManager uwbManager = mContext.getSystemService(UwbManager.class); + if (uwbManager != null) { + mDisableUWBOnSatelliteEnabled = true; + mUwbStateEnabled = uwbManager.isUwbEnabled(); + final long identity = Binder.clearCallingIdentity(); + try { + uwbManager.registerAdapterStateCallback(mContext.getMainExecutor(), + uwbAdapterStateCallback); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + logd("mDisableBTOnSatelliteEnabled: " + mDisableBTOnSatelliteEnabled + + " mDisableNFCOnSatelliteEnabled: " + mDisableNFCOnSatelliteEnabled + + " mDisableWifiOnSatelliteEnabled: " + mDisableWifiOnSatelliteEnabled + + " mDisableUWBOnSatelliteEnabled: " + mDisableUWBOnSatelliteEnabled); + + logd("mBTStateEnabled: " + mBTStateEnabled + + " mNfcStateEnabled: " + mNfcStateEnabled + + " mWifiStateEnabled: " + mWifiStateEnabled + + " mUwbStateEnabled: " + mUwbStateEnabled); + } + } + } + + protected class UwbAdapterStateCallback implements UwbManager.AdapterStateCallback { + + public String toString(int state) { + switch (state) { + case UwbManager.AdapterStateCallback.STATE_DISABLED: + return "Disabled"; + + case UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE: + return "Inactive"; + + case UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE: + return "Active"; + + default: + return ""; + } + } + + @Override + public void onStateChanged(int state, int reason) { + logd("UwbAdapterStateCallback#onStateChanged() called, state = " + toString(state)); + logd("Adapter state changed reason " + String.valueOf(reason)); + synchronized (mRadioStateLock) { + if (state == UwbManager.AdapterStateCallback.STATE_DISABLED) { + mUwbStateEnabled = false; + evaluateToSendSatelliteEnabledSuccess(); + } else { + mUwbStateEnabled = true; + } + logd("mUwbStateEnabled: " + mUwbStateEnabled); + } + } + } + + protected class BTWifiNFCStateReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action == null) { + logd("BTWifiNFCStateReceiver NULL action for intent " + intent); + return; + } + + switch (action) { + case BluetoothAdapter.ACTION_STATE_CHANGED: + int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + logd("Bluetooth state updated to " + btState); + synchronized (mRadioStateLock) { + if (btState == BluetoothAdapter.STATE_OFF) { + mBTStateEnabled = false; + evaluateToSendSatelliteEnabledSuccess(); + } else if (btState == BluetoothAdapter.STATE_ON) { + mBTStateEnabled = true; + } + 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) { + if (nfcState == NfcAdapter.STATE_ON) { + mNfcStateEnabled = true; + } else if (nfcState == NfcAdapter.STATE_OFF) { + mNfcStateEnabled = false; + evaluateToSendSatelliteEnabledSuccess(); + } + 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) { + if (wifiState == WifiManager.WIFI_STATE_ENABLED) { + mWifiStateEnabled = true; + } else if (wifiState == WifiManager.WIFI_STATE_DISABLED) { + mWifiStateEnabled = false; + evaluateToSendSatelliteEnabledSuccess(); + } + logd("mWifiStateEnabled: " + mWifiStateEnabled); + } + break; + default: + break; + } + } + } + + private static final class SatelliteControllerHandlerRequest { + /** The argument to use for the request */ + public @NonNull Object argument; + /** The caller needs to specify the phone to be used for the request */ + public @NonNull Phone phone; + /** The result of the request that is run on the main thread */ + public @Nullable Object result; + + SatelliteControllerHandlerRequest(Object argument, Phone phone) { + this.argument = argument; + this.phone = phone; + } + } + + private static final class RequestSatelliteEnabledArgument { + public boolean enableSatellite; + public boolean enableDemoMode; + @NonNull public Consumer callback; + + RequestSatelliteEnabledArgument(boolean enableSatellite, boolean enableDemoMode, + Consumer callback) { + this.enableSatellite = enableSatellite; + this.enableDemoMode = enableDemoMode; + this.callback = callback; + } + } + + private static final class ProvisionSatelliteServiceArgument { + @NonNull public String token; + @NonNull public byte[] provisionData; + @NonNull public Consumer callback; + public int subId; + + ProvisionSatelliteServiceArgument(String token, byte[] provisionData, + Consumer callback, int subId) { + this.token = token; + this.provisionData = provisionData; + this.callback = callback; + this.subId = subId; + } + } + + /** + * Arguments to send to SatelliteTransmissionUpdate registrants + */ + public static final class SatelliteTransmissionUpdateArgument { + @NonNull public Consumer errorCallback; + @NonNull public ISatelliteTransmissionUpdateCallback callback; + public int subId; + + SatelliteTransmissionUpdateArgument(Consumer errorCallback, + ISatelliteTransmissionUpdateCallback callback, int subId) { + this.errorCallback = errorCallback; + this.callback = callback; + this.subId = subId; + } + } + + @Override + public void handleMessage(Message msg) { + SatelliteControllerHandlerRequest request; + Message onCompleted; + AsyncResult ar; + + switch(msg.what) { + case CMD_START_SATELLITE_TRANSMISSION_UPDATES: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = + obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, request); + mPointingAppController.startSatelliteTransmissionUpdates(onCompleted, + request.phone); + break; + } + + case EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE: { + handleStartSatelliteTransmissionUpdatesDone((AsyncResult) msg.obj); + break; + } + + case CMD_STOP_SATELLITE_TRANSMISSION_UPDATES: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = + obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, request); + mPointingAppController.stopSatelliteTransmissionUpdates(onCompleted, request.phone); + break; + } + + case EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, + "stopSatelliteTransmissionUpdates"); + ((Consumer) request.argument).accept(error); + break; + } + + case CMD_PROVISION_SATELLITE_SERVICE: { + request = (SatelliteControllerHandlerRequest) msg.obj; + ProvisionSatelliteServiceArgument argument = + (ProvisionSatelliteServiceArgument) request.argument; + if (mSatelliteProvisionCallbacks.containsKey(argument.subId)) { + argument.callback.accept( + SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS); + notifyRequester(request); + break; + } + mSatelliteProvisionCallbacks.put(argument.subId, argument.callback); + onCompleted = obtainMessage(EVENT_PROVISION_SATELLITE_SERVICE_DONE, request); + // Log the current time for provision triggered + mProvisionMetricsStats.setProvisioningStartTime(); + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface.provisionSatelliteService(argument.token, + argument.provisionData, onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.provisionSatelliteService(onCompleted, argument.token); + } else { + loge("provisionSatelliteService: No phone object"); + argument.callback.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + notifyRequester(request); + mProvisionMetricsStats + .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE) + .reportProvisionMetrics(); + mControllerMetricsStats.reportProvisionCount( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + break; + } + + case EVENT_PROVISION_SATELLITE_SERVICE_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int errorCode = SatelliteServiceUtils.getSatelliteError(ar, + "provisionSatelliteService"); + handleEventProvisionSatelliteServiceDone( + (ProvisionSatelliteServiceArgument) request.argument, errorCode); + notifyRequester(request); + break; + } + + case CMD_DEPROVISION_SATELLITE_SERVICE: { + request = (SatelliteControllerHandlerRequest) msg.obj; + ProvisionSatelliteServiceArgument argument = + (ProvisionSatelliteServiceArgument) request.argument; + onCompleted = obtainMessage(EVENT_DEPROVISION_SATELLITE_SERVICE_DONE, request); + if (argument.callback != null) { + mProvisionMetricsStats.setProvisioningStartTime(); + } + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface + .deprovisionSatelliteService(argument.token, onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.deprovisionSatelliteService(onCompleted, argument.token); + } else { + loge("deprovisionSatelliteService: No phone object"); + if (argument.callback != null) { + argument.callback.accept( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + mProvisionMetricsStats + .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE) + .reportProvisionMetrics(); + mControllerMetricsStats.reportDeprovisionCount( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + } + break; + } + + case EVENT_DEPROVISION_SATELLITE_SERVICE_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int errorCode = SatelliteServiceUtils.getSatelliteError(ar, + "deprovisionSatelliteService"); + handleEventDeprovisionSatelliteServiceDone( + (ProvisionSatelliteServiceArgument) request.argument, errorCode); + break; + } + + case CMD_SET_SATELLITE_ENABLED: { + request = (SatelliteControllerHandlerRequest) msg.obj; + handleSatelliteEnabled(request); + break; + } + + case EVENT_SET_SATELLITE_ENABLED_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + RequestSatelliteEnabledArgument argument = + (RequestSatelliteEnabledArgument) request.argument; + int error = SatelliteServiceUtils.getSatelliteError(ar, "setSatelliteEnabled"); + logd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error); + + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + if (argument.enableSatellite) { + synchronized (mSatelliteEnabledRequestLock) { + mWaitingForRadioDisabled = true; + } + setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_TRUE); + + /** + * TODO for NTN-based satellites: Check if satellite is acquired. + */ + if (mNeedsSatellitePointing) { + mPointingAppController.startPointingUI(false); + } + evaluateToSendSatelliteEnabledSuccess(); + } else { + synchronized (mSatelliteEnabledRequestLock) { + if (mSatelliteEnabledRequest != null && + mSatelliteEnabledRequest.enableSatellite == true && + argument.enableSatellite == false && mWaitingForRadioDisabled) { + // Previous mSatelliteEnabledRequest is successful but waiting for + // all radios to be turned off. + mSatelliteEnabledRequest.callback.accept( + SatelliteManager.SATELLITE_ERROR_NONE); + } + } + resetSatelliteEnabledRequest(); + + setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE); + setDemoModeEnabled(argument.enableDemoMode); + synchronized (mIsSatelliteEnabledLock) { + mIsSatelliteEnabled = argument.enableSatellite; + } + // If satellite is disabled, send success to callback immediately + argument.callback.accept(error); + updateSatelliteEnabledState( + argument.enableSatellite, "EVENT_SET_SATELLITE_ENABLED_DONE"); + } + } else { + synchronized (mSatelliteEnabledRequestLock) { + if (mSatelliteEnabledRequest != null && + mSatelliteEnabledRequest.enableSatellite == true && + argument.enableSatellite == false && mWaitingForRadioDisabled) { + // Previous mSatelliteEnabledRequest is successful but waiting for + // all radios to be turned off. + mSatelliteEnabledRequest.callback.accept( + SatelliteManager.SATELLITE_ERROR_NONE); + } + } + resetSatelliteEnabledRequest(); + + // If Satellite enable/disable request returned Error, no need to wait for radio + argument.callback.accept(error); + } + + if (argument.enableSatellite) { + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + mControllerMetricsStats.onSatelliteEnabled(); + mControllerMetricsStats.reportServiceEnablementSuccessCount(); + } else { + mControllerMetricsStats.reportServiceEnablementFailCount(); + } + SessionMetricsStats.getInstance() + .setInitializationResult(error) + .setRadioTechnology(SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY) + .reportSessionMetrics(); + } else { + mControllerMetricsStats.onSatelliteDisabled(); + } + break; + } + + case CMD_IS_SATELLITE_ENABLED: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = obtainMessage(EVENT_IS_SATELLITE_ENABLED_DONE, request); + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface.requestIsSatelliteEnabled(onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.isSatellitePowerOn(onCompleted); + } else { + loge("isSatelliteEnabled: No phone object"); + ((ResultReceiver) request.argument).send( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + } + break; + } + + case EVENT_IS_SATELLITE_ENABLED_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, + "isSatelliteEnabled"); + Bundle bundle = new Bundle(); + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + if (ar.result == null) { + loge("isSatelliteEnabled: result is null"); + error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } else { + boolean enabled = ((int[]) ar.result)[0] == 1; + if (DBG) logd("isSatelliteEnabled: " + enabled); + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, enabled); + updateSatelliteEnabledState(enabled, "EVENT_IS_SATELLITE_ENABLED_DONE"); + } + } else if (error == SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED) { + updateSatelliteSupportedStateWhenSatelliteServiceConnected(false); + } + ((ResultReceiver) request.argument).send(error, bundle); + break; + } + + case CMD_IS_SATELLITE_SUPPORTED: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = obtainMessage(EVENT_IS_SATELLITE_SUPPORTED_DONE, request); + + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface.requestIsSatelliteSupported(onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.isSatelliteSupported(onCompleted); + } else { + loge("isSatelliteSupported: No phone object"); + ((ResultReceiver) request.argument).send( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + } + break; + } + + case EVENT_IS_SATELLITE_SUPPORTED_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, "isSatelliteSupported"); + Bundle bundle = new Bundle(); + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + if (ar.result == null) { + loge("isSatelliteSupported: result is null"); + error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } else { + boolean supported = (boolean) ar.result; + if (DBG) logd("isSatelliteSupported: " + supported); + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, supported); + updateSatelliteSupportedStateWhenSatelliteServiceConnected(supported); + } + } + ((ResultReceiver) request.argument).send(error, bundle); + break; + } + + case CMD_GET_SATELLITE_CAPABILITIES: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = obtainMessage(EVENT_GET_SATELLITE_CAPABILITIES_DONE, request); + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface.requestSatelliteCapabilities(onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.getSatelliteCapabilities(onCompleted); + } else { + loge("getSatelliteCapabilities: No phone object"); + ((ResultReceiver) request.argument).send( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + } + break; + } + + case EVENT_GET_SATELLITE_CAPABILITIES_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, + "getSatelliteCapabilities"); + Bundle bundle = new Bundle(); + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + if (ar.result == null) { + loge("getSatelliteCapabilities: result is null"); + error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } else { + SatelliteCapabilities capabilities = (SatelliteCapabilities) ar.result; + synchronized (mNeedsSatellitePointingLock) { + mNeedsSatellitePointing = capabilities.isPointingRequired(); + } + if (DBG) logd("getSatelliteCapabilities: " + capabilities); + bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES, + capabilities); + synchronized (mSatelliteCapabilitiesLock) { + mSatelliteCapabilities = capabilities; + } + } + } + ((ResultReceiver) request.argument).send(error, bundle); + break; + } + + case CMD_IS_SATELLITE_COMMUNICATION_ALLOWED: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = + obtainMessage(EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE, request); + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface + .requestIsSatelliteCommunicationAllowedForCurrentLocation( + onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.isSatelliteCommunicationAllowedForCurrentLocation(onCompleted); + } else { + loge("isSatelliteCommunicationAllowedForCurrentLocation: No phone object"); + ((ResultReceiver) request.argument).send( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + } + break; + } + + case EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, + "isSatelliteCommunicationAllowedForCurrentLocation"); + Bundle bundle = new Bundle(); + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + if (ar.result == null) { + loge("isSatelliteCommunicationAllowedForCurrentLocation: result is null"); + error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } else { + boolean communicationAllowed = (boolean) ar.result; + if (DBG) { + logd("isSatelliteCommunicationAllowedForCurrentLocation: " + + communicationAllowed); + } + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED, + communicationAllowed); + } + } + ((ResultReceiver) request.argument).send(error, bundle); + break; + } + + case CMD_GET_TIME_SATELLITE_NEXT_VISIBLE: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = obtainMessage(EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE, + request); + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface + .requestTimeForNextSatelliteVisibility(onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.requestTimeForNextSatelliteVisibility(onCompleted); + } else { + loge("requestTimeForNextSatelliteVisibility: No phone object"); + ((ResultReceiver) request.argument).send( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + } + break; + } + + case EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, + "requestTimeForNextSatelliteVisibility"); + Bundle bundle = new Bundle(); + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + if (ar.result == null) { + loge("requestTimeForNextSatelliteVisibility: result is null"); + error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } else { + int nextVisibilityDuration = ((int[]) ar.result)[0]; + if (DBG) { + logd("requestTimeForNextSatelliteVisibility: " + + nextVisibilityDuration); + } + bundle.putInt(SatelliteManager.KEY_SATELLITE_NEXT_VISIBILITY, + nextVisibilityDuration); + } + } + ((ResultReceiver) request.argument).send(error, bundle); + break; + } + + case EVENT_RADIO_STATE_CHANGED: { + if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF + || 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 result = FunctionalUtils + .ignoreRemoteException(errorCallback::accept); + RequestSatelliteEnabledArgument message = + new RequestSatelliteEnabledArgument(false, false, result); + request = new SatelliteControllerHandlerRequest(message, phone); + handleSatelliteEnabled(request); + } else { + mIsRadioOn = true; + if (!mSatelliteModemInterface.isSatelliteServiceSupported()) { + synchronized (mIsSatelliteSupportedLock) { + if (mIsSatelliteSupported == null) { + ResultReceiver receiver = new ResultReceiver(this) { + @Override + protected void onReceiveResult( + int resultCode, Bundle resultData) { + logd("requestIsSatelliteSupported: resultCode=" + + resultCode); + } + }; + requestIsSatelliteSupported( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver); + } + } + } else { + logd("EVENT_RADIO_STATE_CHANGED: Satellite vendor service is supported." + + " Ignored the event"); + } + } + break; + } + + case CMD_IS_SATELLITE_PROVISIONED: { + request = (SatelliteControllerHandlerRequest) msg.obj; + onCompleted = obtainMessage(EVENT_IS_SATELLITE_PROVISIONED_DONE, request); + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + mSatelliteModemInterface.requestIsSatelliteProvisioned(onCompleted); + break; + } + Phone phone = request.phone; + if (phone != null) { + phone.isSatelliteProvisioned(onCompleted); + } else { + loge("isSatelliteProvisioned: No phone object"); + ((ResultReceiver) request.argument).send( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + } + break; + } + + case EVENT_IS_SATELLITE_PROVISIONED_DONE: { + ar = (AsyncResult) msg.obj; + request = (SatelliteControllerHandlerRequest) ar.userObj; + int error = SatelliteServiceUtils.getSatelliteError(ar, + "isSatelliteProvisioned"); + Bundle bundle = new Bundle(); + if (error == SatelliteManager.SATELLITE_ERROR_NONE) { + if (ar.result == null) { + loge("isSatelliteProvisioned: result is null"); + error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } else { + boolean provisioned = ((int[]) ar.result)[0] == 1; + if (DBG) logd("isSatelliteProvisioned: " + provisioned); + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED, provisioned); + synchronized (mIsSatelliteProvisionedLock) { + mIsSatelliteProvisioned = provisioned; + } + } + } + ((ResultReceiver) request.argument).send(error, bundle); + break; + } + + case EVENT_SATELLITE_PROVISION_STATE_CHANGED: + ar = (AsyncResult) msg.obj; + if (ar.result == null) { + loge("EVENT_SATELLITE_PROVISION_STATE_CHANGED: result is null"); + } else { + handleEventSatelliteProvisionStateChanged((boolean) ar.result); + } + break; + + case EVENT_PENDING_DATAGRAMS: + logd("Received EVENT_PENDING_DATAGRAMS"); + IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + logd("pollPendingSatelliteDatagram result: " + result); + } + }; + pollPendingSatelliteDatagrams( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, internalCallback); + break; + + case EVENT_SATELLITE_MODEM_STATE_CHANGED: + ar = (AsyncResult) msg.obj; + if (ar.result == null) { + loge("EVENT_SATELLITE_MODEM_STATE_CHANGED: result is null"); + } else { + handleEventSatelliteModemStateChanged((int) ar.result); + } + break; + + default: + Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " + + msg.what); + break; + } + } + + private void notifyRequester(SatelliteControllerHandlerRequest request) { + synchronized (request) { + request.notifyAll(); + } + } + + /** + * Request to enable or disable the satellite modem and demo mode. If the satellite modem is + * enabled, this will also disable the cellular modem, and if the satellite modem is disabled, + * this will also re-enable the cellular modem. + * + * @param subId The subId of the subscription to set satellite enabled for. + * @param enableSatellite {@code true} to enable the satellite modem and + * {@code false} to disable. + * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable. + * @param callback The callback to get the error code of the request. + */ + public void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode, + @NonNull IIntegerConsumer callback) { + logd("requestSatelliteEnabled subId: " + subId + " enableSatellite: " + enableSatellite + + " enableDemoMode: " + enableDemoMode); + + Consumer result = FunctionalUtils.ignoreRemoteException(callback::accept); + + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteSupported) { + result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED); + return; + } + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteProvisioned) { + result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED); + return; + } + + if (enableSatellite) { + if (!mIsRadioOn) { + loge("Radio is not on, can not enable satellite"); + result.accept(SatelliteManager.SATELLITE_INVALID_MODEM_STATE); + return; + } + } else { + /* if disable satellite, always assume demo is also disabled */ + enableDemoMode = false; + } + + synchronized (mIsSatelliteEnabledLock) { + if (mIsSatelliteEnabled != null) { + if (mIsSatelliteEnabled == enableSatellite) { + if (enableDemoMode != mIsDemoModeEnabled) { + loge("Received invalid demo mode while satellite session is enabled" + + " enableDemoMode = " + enableDemoMode); + result.accept(SatelliteManager.SATELLITE_INVALID_ARGUMENTS); + return; + } else { + logd("Enable request matches with current state" + + " enableSatellite = " + enableSatellite); + result.accept(SatelliteManager.SATELLITE_ERROR_NONE); + return; + } + } + } + } + + RequestSatelliteEnabledArgument request = + new RequestSatelliteEnabledArgument(enableSatellite, enableDemoMode, result); + /** + * Multiple satellite enabled requests are handled as below: + * 1. If there are no ongoing requests, store current request in mSatelliteEnabledRequest + * 2. If there is a ongoing request, then: + * 1. ongoing request = enable, current request = enable: return IN_PROGRESS error + * 2. ongoing request = disable, current request = disable: return IN_PROGRESS error + * 3. ongoing request = disable, current request = enable: return SATELLITE_ERROR error + * 4. ongoing request = enable, current request = disable: send request to modem + */ + synchronized (mSatelliteEnabledRequestLock) { + if (mSatelliteEnabledRequest == null) { + mSatelliteEnabledRequest = request; + } else if (mSatelliteEnabledRequest.enableSatellite == request.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 " + + "processed. Disable satellite is already in progress."); + result.accept(SatelliteManager.SATELLITE_ERROR); + return; + } + } + + sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, SatelliteServiceUtils.getPhone()); + } + + /** + * Request to get whether the satellite modem is enabled. + * + * @param subId The subId of the subscription to check whether satellite is enabled for. + * @param result The result receiver that returns whether the satellite modem is enabled + * if the request is successful or an error code if the request failed. + */ + public void requestIsSatelliteEnabled(int subId, @NonNull ResultReceiver result) { + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteSupported) { + result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null); + return; + } + + synchronized (mIsSatelliteEnabledLock) { + if (mIsSatelliteEnabled != null) { + /* We have already successfully queried the satellite modem. */ + Bundle bundle = new Bundle(); + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, mIsSatelliteEnabled); + result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle); + return; + } + } + + sendRequestAsync(CMD_IS_SATELLITE_ENABLED, result, SatelliteServiceUtils.getPhone()); + } + + /** + * Get whether the satellite modem is enabled. + * This will return the cached value instead of querying the satellite modem. + * + * @return {@code true} if the satellite modem is enabled and {@code false} otherwise. + */ + public boolean isSatelliteEnabled() { + if (mIsSatelliteEnabled == null) return false; + return mIsSatelliteEnabled; + } + + /** + * Request to get whether the satellite service demo mode is enabled. + * + * @param subId The subId of the subscription to check whether the satellite demo mode + * is enabled for. + * @param result The result receiver that returns whether the satellite demo mode is enabled + * if the request is successful or an error code if the request failed. + */ + public void requestIsDemoModeEnabled(int subId, @NonNull ResultReceiver result) { + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteSupported) { + result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null); + return; + } + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteProvisioned) { + result.send(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED, null); + return; + } + + final Bundle bundle = new Bundle(); + bundle.putBoolean(SatelliteManager.KEY_DEMO_MODE_ENABLED, mIsDemoModeEnabled); + result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle); + } + + /** + * Get whether the satellite service demo mode is enabled. + * + * @return {@code true} if the satellite demo mode is enabled and {@code false} otherwise. + */ + public boolean isDemoModeEnabled() { + return mIsDemoModeEnabled; + } + + /** + * Request to get whether the satellite service is supported on the device. + * + * @param subId The subId of the subscription to check satellite service support for. + * @param result The result receiver that returns whether the satellite service is supported on + * the device if the request is successful or an error code if the request failed. + */ + public void requestIsSatelliteSupported(int subId, @NonNull ResultReceiver result) { + synchronized (mIsSatelliteSupportedLock) { + if (mIsSatelliteSupported != null) { + /* We have already successfully queried the satellite modem. */ + Bundle bundle = new Bundle(); + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, mIsSatelliteSupported); + result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle); + return; + } + } + + sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, result, SatelliteServiceUtils.getPhone()); + } + + /** + * Request to get the {@link SatelliteCapabilities} of the satellite service. + * + * @param subId The subId of the subscription to get the satellite capabilities for. + * @param result The result receiver that returns the {@link SatelliteCapabilities} + * if the request is successful or an error code if the request failed. + */ + public void requestSatelliteCapabilities(int subId, @NonNull ResultReceiver result) { + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteSupported) { + result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null); + return; + } + + synchronized (mSatelliteCapabilitiesLock) { + if (mSatelliteCapabilities != null) { + Bundle bundle = new Bundle(); + bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES, + mSatelliteCapabilities); + result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle); + return; + } + } + + sendRequestAsync(CMD_GET_SATELLITE_CAPABILITIES, result, SatelliteServiceUtils.getPhone()); + } + + /** + * Start receiving satellite transmission updates. + * This can be called by the pointing UI when the user starts pointing to the satellite. + * Modem should continue to report the pointing input as the device or satellite moves. + * + * @param subId The subId of the subscription to start satellite transmission updates for. + * @param errorCallback The callback to get the error code of the request. + * @param callback The callback to notify of satellite transmission updates. + */ + public void startSatelliteTransmissionUpdates(int subId, + @NonNull IIntegerConsumer errorCallback, + @NonNull ISatelliteTransmissionUpdateCallback callback) { + Consumer result = FunctionalUtils.ignoreRemoteException(errorCallback::accept); + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteSupported) { + result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED); + return; + } + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteProvisioned) { + result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED); + return; + } + + Phone phone = SatelliteServiceUtils.getPhone(); + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + mPointingAppController.registerForSatelliteTransmissionUpdates(validSubId, callback, phone); + sendRequestAsync(CMD_START_SATELLITE_TRANSMISSION_UPDATES, + new SatelliteTransmissionUpdateArgument(result, callback, validSubId), phone); + } + + /** + * Stop receiving satellite transmission updates. + * This can be called by the pointing UI when the user stops pointing to the satellite. + * + * @param subId The subId of the subscription to stop satellite transmission updates for. + * @param errorCallback The callback to get the error code of the request. + * @param callback The callback that was passed to {@link #startSatelliteTransmissionUpdates( + * int, IIntegerConsumer, ISatelliteTransmissionUpdateCallback)}. + */ + public void stopSatelliteTransmissionUpdates(int subId, @NonNull IIntegerConsumer errorCallback, + @NonNull ISatelliteTransmissionUpdateCallback callback) { + Consumer result = FunctionalUtils.ignoreRemoteException(errorCallback::accept); + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteSupported) { + result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED); + return; + } + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteProvisioned) { + result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED); + return; + } + + Phone phone = SatelliteServiceUtils.getPhone(); + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + mPointingAppController.unregisterForSatelliteTransmissionUpdates( + validSubId, result, callback, phone); + + // Even if handler is null - which means there are no listeners, the modem command to stop + // satellite transmission updates might have failed. The callers might want to retry + // sending the command. Thus, we always need to send this command to the modem. + sendRequestAsync(CMD_STOP_SATELLITE_TRANSMISSION_UPDATES, result, phone); + } + + /** + * Register the subscription with a satellite provider. + * This is needed to register the subscription if the provider allows dynamic registration. + * + * @param subId The subId of the subscription to be provisioned. + * @param token The token to be used as a unique identifier for provisioning with satellite + * gateway. + * @param provisionData Data from the provisioning app that can be used by provisioning server + * @param callback The callback to get the error code of the request. + * + * @return The signal transport used by the caller to cancel the provision request, + * or {@code null} if the request failed. + */ + @Nullable public ICancellationSignal provisionSatelliteService(int subId, + @NonNull String token, @NonNull byte[] provisionData, + @NonNull IIntegerConsumer callback) { + Consumer result = FunctionalUtils.ignoreRemoteException(callback::accept); + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return null; + } + if (!satelliteSupported) { + result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED); + return null; + } + + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + if (mSatelliteProvisionCallbacks.containsKey(validSubId)) { + result.accept(SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS); + return null; + } + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned != null && satelliteProvisioned) { + result.accept(SatelliteManager.SATELLITE_ERROR_NONE); + return null; + } + + Phone phone = SatelliteServiceUtils.getPhone(); + sendRequestAsync(CMD_PROVISION_SATELLITE_SERVICE, + new ProvisionSatelliteServiceArgument(token, provisionData, result, validSubId), + phone); + + ICancellationSignal cancelTransport = CancellationSignal.createTransport(); + CancellationSignal.fromTransport(cancelTransport).setOnCancelListener(() -> { + sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE, + new ProvisionSatelliteServiceArgument(token, provisionData, null, + validSubId), phone); + mProvisionMetricsStats.setIsCanceled(true); + }); + return cancelTransport; + } + + /** + * Unregister the device/subscription with the satellite provider. + * This is needed if the provider allows dynamic registration. Once deprovisioned, + * {@link android.telephony.satellite.SatelliteProvisionStateCallback + * #onSatelliteProvisionStateChanged(boolean)} + * should report as deprovisioned. + * + * @param subId The subId of the subscription to be deprovisioned. + * @param token The token of the device/subscription to be deprovisioned. + * @param callback The callback to get the error code of the request. + */ + public void deprovisionSatelliteService(int subId, + @NonNull String token, @NonNull IIntegerConsumer callback) { + Consumer result = FunctionalUtils.ignoreRemoteException(callback::accept); + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteSupported) { + result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED); + return; + } + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteProvisioned) { + result.accept(SatelliteManager.SATELLITE_ERROR_NONE); + return; + } + + Phone phone = SatelliteServiceUtils.getPhone(); + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE, + new ProvisionSatelliteServiceArgument(token, null, result, validSubId), + phone); + } + + /** + * Registers for the satellite provision state changed. + * + * @param subId The subId of the subscription to register for provision state changed. + * @param callback The callback to handle the satellite provision state changed event. + * + * @return The {@link SatelliteManager.SatelliteError} result of the operation. + */ + @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged(int subId, + @NonNull ISatelliteProvisionStateCallback callback) { + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } + if (!satelliteSupported) { + return SatelliteManager.SATELLITE_NOT_SUPPORTED; + } + + mSatelliteProvisionStateChangedListeners.put(callback.asBinder(), callback); + return SatelliteManager.SATELLITE_ERROR_NONE; + } + + /** + * Unregisters for the satellite provision state changed. + * If callback was not registered before, the request will be ignored. + * + * @param subId The subId of the subscription to unregister for provision state changed. + * @param callback The callback that was passed to + * {@link #registerForSatelliteProvisionStateChanged(int, ISatelliteProvisionStateCallback)}. + */ + public void unregisterForSatelliteProvisionStateChanged( + int subId, @NonNull ISatelliteProvisionStateCallback callback) { + mSatelliteProvisionStateChangedListeners.remove(callback.asBinder()); + } + + /** + * Request to get whether the device is provisioned with a satellite provider. + * + * @param subId The subId of the subscription to get whether the device is provisioned for. + * @param result The result receiver that returns whether the device is provisioned with a + * satellite provider if the request is successful or an error code if the + * request failed. + */ + public void requestIsSatelliteProvisioned(int subId, @NonNull ResultReceiver result) { + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteSupported) { + result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null); + return; + } + + synchronized (mIsSatelliteProvisionedLock) { + if (mIsSatelliteProvisioned != null) { + Bundle bundle = new Bundle(); + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED, + mIsSatelliteProvisioned); + result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle); + return; + } + } + + sendRequestAsync(CMD_IS_SATELLITE_PROVISIONED, result, SatelliteServiceUtils.getPhone()); + } + + /** + * Registers for modem state changed from satellite modem. + * + * @param subId The subId of the subscription to register for satellite modem state changed. + * @param callback The callback to handle the satellite modem state changed event. + * + * @return The {@link SatelliteManager.SatelliteError} result of the operation. + */ + @SatelliteManager.SatelliteError public int registerForSatelliteModemStateChanged(int subId, + @NonNull ISatelliteStateCallback callback) { + if (mSatelliteSessionController != null) { + mSatelliteSessionController.registerForSatelliteModemStateChanged(callback); + } else { + loge("registerForSatelliteModemStateChanged: mSatelliteSessionController" + + " is not initialized yet"); + return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + } + return SatelliteManager.SATELLITE_ERROR_NONE; + } + + /** + * Unregisters for modem state changed from satellite modem. + * If callback was not registered before, the request will be ignored. + * + * @param subId The subId of the subscription to unregister for satellite modem state changed. + * @param callback The callback that was passed to + * {@link #registerForSatelliteModemStateChanged(int, ISatelliteStateCallback)}. + */ + public void unregisterForSatelliteModemStateChanged(int subId, + @NonNull ISatelliteStateCallback callback) { + if (mSatelliteSessionController != null) { + mSatelliteSessionController.unregisterForSatelliteModemStateChanged(callback); + } else { + loge("registerForSatelliteModemStateChanged: mSatelliteSessionController" + + " is not initialized yet"); + } + } + + /** + * Register to receive incoming datagrams over satellite. + * + * @param subId The subId of the subscription to register for incoming satellite datagrams. + * @param callback The callback to handle incoming datagrams over satellite. + * + * @return The {@link SatelliteManager.SatelliteError} result of the operation. + */ + @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId, + @NonNull ISatelliteDatagramCallback callback) { + return mDatagramController.registerForSatelliteDatagram(subId, callback); + } + + /** + * Unregister to stop receiving incoming datagrams over satellite. + * If callback was not registered before, the request will be ignored. + * + * @param subId The subId of the subscription to unregister for incoming satellite datagrams. + * @param callback The callback that was passed to + * {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}. + */ + public void unregisterForSatelliteDatagram(int subId, + @NonNull ISatelliteDatagramCallback callback) { + mDatagramController.unregisterForSatelliteDatagram(subId, callback); + } + + /** + * Poll pending satellite datagrams over satellite. + * + * This method requests modem to check if there are any pending datagrams to be received over + * satellite. If there are any incoming datagrams, they will be received via + * {@link android.telephony.satellite.SatelliteDatagramCallback#onSatelliteDatagramReceived( + * long, SatelliteDatagram, int, Consumer)} + * + * @param subId The subId of the subscription used for receiving datagrams. + * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request. + */ + public void pollPendingSatelliteDatagrams(int subId, @NonNull IIntegerConsumer callback) { + Consumer result = FunctionalUtils.ignoreRemoteException(callback::accept); + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteProvisioned) { + result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED); + return; + } + + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + mDatagramController.pollPendingSatelliteDatagrams(validSubId, result); + } + + /** + * Send datagram over satellite. + * + * Gateway encodes SOS message or location sharing message into a datagram and passes it as + * input to this method. Datagram received here will be passed down to modem without any + * encoding or encryption. + * + * @param subId The subId of the subscription to send satellite datagrams for. + * @param datagramType datagram type indicating whether the datagram is of type + * SOS_SMS or LOCATION_SHARING. + * @param datagram encoded gateway datagram which is encrypted by the caller. + * Datagram will be passed down to modem without any encoding or encryption. + * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in + * full screen mode. + * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request. + */ + public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType, + SatelliteDatagram datagram, boolean needFullScreenPointingUI, + @NonNull IIntegerConsumer callback) { + Consumer result = FunctionalUtils.ignoreRemoteException(callback::accept); + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + if (!satelliteProvisioned) { + result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED); + return; + } + + /** + * TODO for NTN-based satellites: Check if satellite is acquired. + */ + if (mNeedsSatellitePointing) { + mPointingAppController.startPointingUI(needFullScreenPointingUI); + } + + final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); + mDatagramController.sendSatelliteDatagram(validSubId, datagramType, datagram, + needFullScreenPointingUI, result); + } + + /** + * Request to get whether satellite communication is allowed for the current location. + * + * @param subId The subId of the subscription to check whether satellite communication is + * allowed for the current location for. + * @param result The result receiver that returns whether satellite communication is allowed + * for the current location if the request is successful or an error code + * if the request failed. + */ + public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId, + @NonNull ResultReceiver result) { + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteSupported) { + result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null); + return; + } + + sendRequestAsync( + CMD_IS_SATELLITE_COMMUNICATION_ALLOWED, result, SatelliteServiceUtils.getPhone()); + } + + /** + * Request to get the time after which the satellite will be visible. + * + * @param subId The subId to get the time after which the satellite will be visible for. + * @param result The result receiver that returns the time after which the satellite will + * be visible if the request is successful or an error code if the request failed. + */ + public void requestTimeForNextSatelliteVisibility(int subId, @NonNull ResultReceiver result) { + Boolean satelliteSupported = isSatelliteSupportedInternal(); + if (satelliteSupported == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteSupported) { + result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null); + return; + } + + Boolean satelliteProvisioned = isSatelliteProvisioned(); + if (satelliteProvisioned == null) { + result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null); + return; + } + if (!satelliteProvisioned) { + result.send(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED, null); + return; + } + + Phone phone = SatelliteServiceUtils.getPhone(); + sendRequestAsync(CMD_GET_TIME_SATELLITE_NEXT_VISIBLE, result, phone); + } + + /** + * Inform whether the device is aligned with satellite for demo mode. + * + * @param subId The subId of the subscription. + * @param isAligned {@true} means device is aligned with the satellite, otherwise {@false}. + */ + public void onDeviceAlignedWithSatellite(@NonNull int subId, @NonNull boolean isAligned) { + mDatagramController.onDeviceAlignedWithSatellite(isAligned); + } + + /** + * This API can be used by only CTS to update satellite vendor service package name. + * + * @param servicePackageName The package name of the satellite vendor service. + * @return {@code true} if the satellite vendor service is set successfully, + * {@code false} otherwise. + */ + public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) { + boolean result = mSatelliteModemInterface.setSatelliteServicePackageName( + servicePackageName); + if (result) { + logd("setSatelliteServicePackageName: Resetting cached states"); + + // 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); + } + return result; + } + + /** + * This API can be used by only CTS to update the timeout duration in milliseconds that + * satellite should stay at listening mode to wait for the next incoming page before disabling + * listening mode. + * + * @param timeoutMillis The timeout duration in millisecond. + * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise. + */ + public boolean setSatelliteListeningTimeoutDuration(long timeoutMillis) { + if (mSatelliteSessionController == null) { + loge("mSatelliteSessionController is not initialized yet"); + return false; + } + return mSatelliteSessionController.setSatelliteListeningTimeoutDuration(timeoutMillis); + } + + /** + * This API can be used by only CTS to update the timeout duration in milliseconds whether + * the device is aligned with the satellite for demo mode + * + * @param timeoutMillis The timeout duration in millisecond. + * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise. + */ + public boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) { + return mDatagramController.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis); + } + + /** + * This API can be used by only CTS to update satellite gateway service package name. + * + * @param servicePackageName The package name of the satellite gateway service. + * @return {@code true} if the satellite gateway service is set successfully, + * {@code false} otherwise. + */ + public boolean setSatelliteGatewayServicePackageName(@Nullable String servicePackageName) { + if (mSatelliteSessionController == null) { + loge("mSatelliteSessionController is not initialized yet"); + return false; + } + return mSatelliteSessionController.setSatelliteGatewayServicePackageName( + servicePackageName); + } + + /** + * This API can be used by only CTS to update satellite pointing UI app package and class names. + * + * @param packageName The package name of the satellite pointing UI app. + * @param className The class name of the satellite pointing UI app. + * @return {@code true} if the satellite pointing UI app package and class is set successfully, + * {@code false} otherwise. + */ + public boolean setSatellitePointingUiClassName( + @Nullable String packageName, @Nullable String className) { + return mPointingAppController.setSatellitePointingUiClassName(packageName, className); + } + + /** + * This function is used by {@link SatelliteModemInterface} to notify + * {@link SatelliteController} that the satellite vendor service was just connected. + *

+ * {@link SatelliteController} will send requests to satellite modem to check whether it support + * satellite and whether it is provisioned. {@link SatelliteController} will use these cached + * values to serve requests from its clients. + *

+ * Because satellite vendor service might have just come back from a crash, we need to disable + * the satellite modem so that resources will be cleaned up and internal states will be reset. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void onSatelliteServiceConnected() { + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + synchronized (mIsSatelliteSupportedLock) { + if (mIsSatelliteSupported == null) { + ResultReceiver receiver = new ResultReceiver(this) { + @Override + protected void onReceiveResult( + int resultCode, Bundle resultData) { + logd("requestIsSatelliteSupported: resultCode=" + + resultCode); + } + }; + requestIsSatelliteSupported( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver); + } + } + } else { + logd("onSatelliteServiceConnected: Satellite vendor service is not supported." + + " Ignored the event"); + } + } + + /** + * @return {@code true} is satellite is supported on the device, {@code false} otherwise. + */ + public boolean isSatelliteSupported() { + Boolean supported = isSatelliteSupportedInternal(); + return (supported != null ? supported : false); + } + + /** + * 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. + */ + private Boolean isSatelliteSupportedInternal() { + synchronized (mIsSatelliteSupportedLock) { + if (mIsSatelliteSupported != null) { + /* We have already successfully queried the satellite modem. */ + return mIsSatelliteSupported; + } + } + /** + * We have not successfully checked whether the modem supports satellite service. + * Thus, we need to retry it now. + */ + requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + new ResultReceiver(this) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + logd("requestIsSatelliteSupported: resultCode=" + resultCode); + } + }); + return null; + } + + private void handleEventProvisionSatelliteServiceDone( + @NonNull ProvisionSatelliteServiceArgument arg, + @SatelliteManager.SatelliteError int result) { + logd("handleEventProvisionSatelliteServiceDone: result=" + + result + ", subId=" + arg.subId); + + Consumer callback = mSatelliteProvisionCallbacks.remove(arg.subId); + if (callback == null) { + loge("handleEventProvisionSatelliteServiceDone: callback is null for subId=" + + arg.subId); + mProvisionMetricsStats + .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE) + .setIsProvisionRequest(true) + .reportProvisionMetrics(); + mControllerMetricsStats.reportProvisionCount( + SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + return; + } + callback.accept(result); + } + + private void handleEventDeprovisionSatelliteServiceDone( + @NonNull ProvisionSatelliteServiceArgument arg, + @SatelliteManager.SatelliteError int result) { + if (arg == null) { + loge("handleEventDeprovisionSatelliteServiceDone: arg is null"); + return; + } + logd("handleEventDeprovisionSatelliteServiceDone: result=" + + result + ", subId=" + arg.subId); + + if (arg.callback != null) { + arg.callback.accept(result); + mProvisionMetricsStats.setResultCode(result) + .setIsProvisionRequest(false) + .reportProvisionMetrics(); + mControllerMetricsStats.reportDeprovisionCount(result); + } + } + + private void handleStartSatelliteTransmissionUpdatesDone(@NonNull AsyncResult ar) { + SatelliteControllerHandlerRequest request = (SatelliteControllerHandlerRequest) ar.userObj; + SatelliteTransmissionUpdateArgument arg = + (SatelliteTransmissionUpdateArgument) request.argument; + int errorCode = SatelliteServiceUtils.getSatelliteError(ar, + "handleStartSatelliteTransmissionUpdatesDone"); + arg.errorCallback.accept(errorCode); + + if (errorCode != SatelliteManager.SATELLITE_ERROR_NONE) { + mPointingAppController.setStartedSatelliteTransmissionUpdates(false); + // We need to remove the callback from our listener list since the caller might not call + // stopSatelliteTransmissionUpdates to unregister the callback in case of failure. + mPointingAppController.unregisterForSatelliteTransmissionUpdates(arg.subId, + arg.errorCallback, arg.callback, request.phone); + } else { + mPointingAppController.setStartedSatelliteTransmissionUpdates(true); + } + } + + /** + * Posts the specified command to be executed on the main thread and returns immediately. + * + * @param command command to be executed on the main thread + * @param argument additional parameters required to perform of the operation + * @param phone phone object used to perform the operation. + */ + private void sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone) { + SatelliteControllerHandlerRequest request = new SatelliteControllerHandlerRequest( + argument, phone); + Message msg = this.obtainMessage(command, request); + msg.sendToTarget(); + } + + /** + * Posts the specified command to be executed on the main thread. As this is a synchronous + * request, it waits until the request is complete and then return the result. + * + * @param command command to be executed on the main thread + * @param argument additional parameters required to perform of the operation + * @param phone phone object used to perform the operation. + * @return result of the operation + */ + private @Nullable Object sendRequest(int command, @NonNull Object argument, + @Nullable Phone phone) { + if (Looper.myLooper() == this.getLooper()) { + throw new RuntimeException("This method will deadlock if called from the main thread"); + } + + SatelliteControllerHandlerRequest request = new SatelliteControllerHandlerRequest( + argument, phone); + Message msg = this.obtainMessage(command, request); + msg.sendToTarget(); + + synchronized (request) { + while(request.result == null) { + try { + request.wait(); + } catch (InterruptedException e) { + // Do nothing, go back and wait until the request is complete. + } + } + } + return request.result; + } + + /** + * Check if satellite is provisioned for a subscription on the device. + * @return true if satellite is provisioned on the given subscription else return false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected Boolean isSatelliteProvisioned() { + synchronized (mIsSatelliteProvisionedLock) { + if (mIsSatelliteProvisioned != null) { + return mIsSatelliteProvisioned; + } + } + + requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + new ResultReceiver(this) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + logd("requestIsSatelliteProvisioned: resultCode=" + resultCode); + } + }); + return null; + } + + private void handleSatelliteEnabled(SatelliteControllerHandlerRequest request) { + RequestSatelliteEnabledArgument argument = + (RequestSatelliteEnabledArgument) request.argument; + 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 { + loge("requestSatelliteEnabled: No phone object"); + argument.callback.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + } + + private void updateSatelliteSupportedStateWhenSatelliteServiceConnected(boolean supported) { + synchronized (mIsSatelliteSupportedLock) { + mIsSatelliteSupported = supported; + } + mSatelliteSessionController = SatelliteSessionController.make( + mContext, getLooper(), supported); + if (supported) { + registerForSatelliteProvisionStateChanged(); + registerForPendingDatagramCount(); + registerForSatelliteModemStateChanged(); + + requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + new ResultReceiver(this) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + logd("requestIsSatelliteProvisioned: resultCode=" + resultCode); + requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + false, false, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + logd("requestSatelliteEnabled: result=" + result); + } + }); + } + }); + requestSatelliteCapabilities(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + new ResultReceiver(this) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + logd("requestSatelliteCapabilities: resultCode=" + resultCode); + } + }); + } + } + + private void updateSatelliteEnabledState(boolean enabled, String caller) { + synchronized (mIsSatelliteEnabledLock) { + mIsSatelliteEnabled = enabled; + } + if (mSatelliteSessionController != null) { + mSatelliteSessionController.onSatelliteEnabledStateChanged(enabled); + mSatelliteSessionController.setDemoMode(mIsDemoModeEnabled); + } else { + loge(caller + ": mSatelliteSessionController is not initialized yet"); + } + } + + private void registerForSatelliteProvisionStateChanged() { + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + if (!mRegisteredForProvisionStateChangedWithSatelliteService.get()) { + mSatelliteModemInterface.registerForSatelliteProvisionStateChanged( + this, EVENT_SATELLITE_PROVISION_STATE_CHANGED, null); + mRegisteredForProvisionStateChangedWithSatelliteService.set(true); + } + } else { + Phone phone = SatelliteServiceUtils.getPhone(); + if (phone == null) { + loge("registerForSatelliteProvisionStateChanged: phone is null"); + } else if (!mRegisteredForProvisionStateChangedWithPhone.get()) { + phone.registerForSatelliteProvisionStateChanged( + this, EVENT_SATELLITE_PROVISION_STATE_CHANGED, null); + mRegisteredForProvisionStateChangedWithPhone.set(true); + } + } + } + + private void registerForPendingDatagramCount() { + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + if (!mRegisteredForPendingDatagramCountWithSatelliteService.get()) { + mSatelliteModemInterface.registerForPendingDatagrams( + this, EVENT_PENDING_DATAGRAMS, null); + mRegisteredForPendingDatagramCountWithSatelliteService.set(true); + } + } else { + Phone phone = SatelliteServiceUtils.getPhone(); + if (phone == null) { + loge("registerForPendingDatagramCount: satellite phone is " + + "not initialized yet"); + } else if (!mRegisteredForPendingDatagramCountWithPhone.get()) { + phone.registerForPendingDatagramCount(this, EVENT_PENDING_DATAGRAMS, null); + mRegisteredForPendingDatagramCountWithPhone.set(true); + } + } + } + + private void registerForSatelliteModemStateChanged() { + if (mSatelliteModemInterface.isSatelliteServiceSupported()) { + if (!mRegisteredForSatelliteModemStateChangedWithSatelliteService.get()) { + mSatelliteModemInterface.registerForSatelliteModemStateChanged( + this, EVENT_SATELLITE_MODEM_STATE_CHANGED, null); + mRegisteredForSatelliteModemStateChangedWithSatelliteService.set(true); + } + } else { + Phone phone = SatelliteServiceUtils.getPhone(); + if (phone == null) { + loge("registerForSatelliteModemStateChanged: satellite phone is " + + "not initialized yet"); + } else if (!mRegisteredForSatelliteModemStateChangedWithPhone.get()) { + phone.registerForSatelliteModemStateChanged( + this, EVENT_SATELLITE_MODEM_STATE_CHANGED, null); + mRegisteredForSatelliteModemStateChangedWithPhone.set(true); + } + } + } + + private void handleEventSatelliteProvisionStateChanged(boolean provisioned) { + logd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned); + + synchronized (mIsSatelliteProvisionedLock) { + mIsSatelliteProvisioned = provisioned; + } + + List toBeRemoved = new ArrayList<>(); + mSatelliteProvisionStateChangedListeners.values().forEach(listener -> { + try { + listener.onSatelliteProvisionStateChanged(provisioned); + } catch (RemoteException e) { + logd("handleSatelliteProvisionStateChangedEvent RemoteException: " + e); + toBeRemoved.add(listener); + } + }); + toBeRemoved.forEach(listener -> { + mSatelliteProvisionStateChangedListeners.remove(listener.asBinder()); + }); + } + + 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); + } + + mDatagramController.onSatelliteModemStateChanged(state); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected void setSettingsKeyForSatelliteMode(int val) { + logd("setSettingsKeyForSatelliteMode val: " + val); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.SATELLITE_MODE_ENABLED, val); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected boolean areAllRadiosDisabled() { + synchronized (mRadioStateLock) { + if ((mDisableBTOnSatelliteEnabled && mBTStateEnabled) + || (mDisableNFCOnSatelliteEnabled && mNfcStateEnabled) + || (mDisableWifiOnSatelliteEnabled && mWifiStateEnabled) + || (mDisableUWBOnSatelliteEnabled && mUwbStateEnabled)) { + logd("All radios are not disabled yet."); + return false; + } + logd("All radios are disabled."); + return true; + } + } + + private void evaluateToSendSatelliteEnabledSuccess() { + logd("evaluateToSendSatelliteEnabledSuccess"); + synchronized (mSatelliteEnabledRequestLock) { + if (areAllRadiosDisabled() && (mSatelliteEnabledRequest != null) + && mWaitingForRadioDisabled) { + logd("Sending success to callback that sent enable satellite request"); + setDemoModeEnabled(mSatelliteEnabledRequest.enableDemoMode); + synchronized (mIsSatelliteEnabledLock) { + mIsSatelliteEnabled = mSatelliteEnabledRequest.enableSatellite; + } + mSatelliteEnabledRequest.callback.accept(SatelliteManager.SATELLITE_ERROR_NONE); + updateSatelliteEnabledState( + mSatelliteEnabledRequest.enableSatellite, + "EVENT_SET_SATELLITE_ENABLED_DONE"); + mSatelliteEnabledRequest = null; + mWaitingForRadioDisabled = false; + } + } + } + + private void resetSatelliteEnabledRequest() { + logd("resetSatelliteEnabledRequest"); + synchronized (mSatelliteEnabledRequestLock) { + mSatelliteEnabledRequest = null; + mWaitingForRadioDisabled = false; + } + } + + 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); + } + } + resetSatelliteEnabledRequest(); + } + } + + private void setDemoModeEnabled(boolean enabled) { + mIsDemoModeEnabled = enabled; + mDatagramController.setDemoMode(mIsDemoModeEnabled); + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..80c67b315a627978d286b2bf7afc5f7ec0ea2727 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java @@ -0,0 +1,1069 @@ +/* + * 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.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +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; +import android.telephony.satellite.SatelliteManager; +import android.telephony.satellite.SatelliteManager.SatelliteException; +import android.telephony.satellite.stub.ISatellite; +import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer; +import android.telephony.satellite.stub.ISatelliteListener; +import android.telephony.satellite.stub.SatelliteService; +import android.text.TextUtils; +import android.util.Pair; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.ExponentialBackoff; +import com.android.internal.telephony.IBooleanConsumer; +import com.android.internal.telephony.IIntegerConsumer; + +import java.util.Arrays; + +/** + * Satellite modem interface to manage connections with the satellite service and HAL interface. + */ +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; + + @NonNull private static SatelliteModemInterface sInstance; + @NonNull private final Context mContext; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @NonNull protected final ExponentialBackoff mExponentialBackoff; + @NonNull private final Object mLock = new Object(); + @NonNull private final SatelliteController mSatelliteController; + /** + * {@code true} to use the vendor satellite service and {@code false} to use the HAL. + */ + private boolean mIsSatelliteServiceSupported; + @Nullable private ISatellite mSatelliteService; + @Nullable private SatelliteServiceConnection mSatelliteServiceConnection; + @NonNull private String mVendorSatellitePackageName = ""; + private boolean mIsBound; + private boolean mIsBinding; + + @NonNull private final RegistrantList mSatelliteProvisionStateChangedRegistrants = + new RegistrantList(); + @NonNull private final RegistrantList mSatellitePositionInfoChangedRegistrants = + new RegistrantList(); + @NonNull private final RegistrantList mDatagramTransferStateChangedRegistrants = + new RegistrantList(); + @NonNull private final RegistrantList mSatelliteModemStateChangedRegistrants = + new RegistrantList(); + @NonNull private final RegistrantList mPendingDatagramsRegistrants = new RegistrantList(); + @NonNull private final RegistrantList mSatelliteDatagramsReceivedRegistrants = + new RegistrantList(); + + @NonNull private final ISatelliteListener mListener = new ISatelliteListener.Stub() { + @Override + public void onSatelliteProvisionStateChanged(boolean provisioned) { + mSatelliteProvisionStateChangedRegistrants.notifyResult(provisioned); + } + + @Override + public void onSatelliteDatagramReceived( + android.telephony.satellite.stub.SatelliteDatagram datagram, int pendingCount) { + mSatelliteDatagramsReceivedRegistrants.notifyResult(new Pair<>( + SatelliteServiceUtils.fromSatelliteDatagram(datagram), pendingCount)); + } + + @Override + public void onPendingDatagrams() { + mPendingDatagramsRegistrants.notifyResult(null); + } + + @Override + public void onSatellitePositionChanged( + android.telephony.satellite.stub.PointingInfo pointingInfo) { + mSatellitePositionInfoChangedRegistrants.notifyResult( + SatelliteServiceUtils.fromPointingInfo(pointingInfo)); + } + + @Override + public void onSatelliteModemStateChanged(int state) { + mSatelliteModemStateChangedRegistrants.notifyResult( + SatelliteServiceUtils.fromSatelliteModemState(state)); + int datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN; + switch (state) { + case SatelliteManager.SATELLITE_MODEM_STATE_IDLE: + datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE; + break; + case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING: + datagramTransferState = + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING; + break; + case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING: + datagramTransferState = + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING; + break; + case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING: + // keep previous state as this could be retrying sending or receiving + break; + } + mDatagramTransferStateChangedRegistrants.notifyResult(datagramTransferState); + } + }; + + /** + * @return The singleton instance of SatelliteModemInterface. + */ + public static SatelliteModemInterface getInstance() { + if (sInstance == null) { + loge("SatelliteModemInterface was not yet initialized."); + } + return sInstance; + } + + /** + * Create the SatelliteModemInterface singleton instance. + * @param context The Context to use to create the SatelliteModemInterface. + * @param satelliteController The singleton instance of SatelliteController. + * @return The singleton instance of SatelliteModemInterface. + */ + public static SatelliteModemInterface make(@NonNull Context context, + SatelliteController satelliteController) { + if (sInstance == null) { + sInstance = new SatelliteModemInterface( + context, satelliteController, Looper.getMainLooper()); + } + return sInstance; + } + + /** + * Create a SatelliteModemInterface to manage connections to the SatelliteService. + * + * @param context The Context for the SatelliteModemInterface. + * @param looper The Looper to run binding retry on. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected SatelliteModemInterface(@NonNull Context context, + SatelliteController satelliteController, @NonNull Looper looper) { + mContext = context; + mIsSatelliteServiceSupported = getSatelliteServiceSupport(); + mSatelliteController = satelliteController; + mExponentialBackoff = new ExponentialBackoff(REBIND_INITIAL_DELAY, REBIND_MAXIMUM_DELAY, + REBIND_MULTIPLIER, looper, () -> { + synchronized (mLock) { + if ((mIsBound && mSatelliteService != null) || mIsBinding) { + return; + } + } + if (mSatelliteServiceConnection != null) { + synchronized (mLock) { + mIsBound = false; + mIsBinding = false; + } + unbindService(); + } + bindService(); + }); + mExponentialBackoff.start(); + logd("Created SatelliteModemInterface. Attempting to bind to SatelliteService."); + bindService(); + } + + /** + * Get the SatelliteService interface, if it exists. + * + * @return The bound ISatellite, or {@code null} if it is not yet connected. + */ + @Nullable public ISatellite getService() { + return mSatelliteService; + } + + @NonNull private String getSatellitePackageName() { + if (!TextUtils.isEmpty(mVendorSatellitePackageName)) { + return mVendorSatellitePackageName; + } + return TextUtils.emptyIfNull(mContext.getResources().getString( + R.string.config_satellite_service_package)); + } + + private boolean getSatelliteServiceSupport() { + return !TextUtils.isEmpty(getSatellitePackageName()); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected void bindService() { + synchronized (mLock) { + if (mIsBinding || mIsBound) return; + mIsBinding = true; + } + String packageName = getSatellitePackageName(); + if (TextUtils.isEmpty(packageName)) { + loge("Unable to bind to the satellite service because the package is undefined."); + // Since the package name comes from static device configs, stop retry because + // rebind will continue to fail without a valid package name. + synchronized (mLock) { + mIsBinding = false; + } + mExponentialBackoff.stop(); + return; + } + Intent intent = new Intent(SatelliteService.SERVICE_INTERFACE); + intent.setPackage(packageName); + + mSatelliteServiceConnection = new SatelliteServiceConnection(); + logd("Binding to " + packageName); + try { + boolean success = mContext.bindService( + intent, mSatelliteServiceConnection, Context.BIND_AUTO_CREATE); + if (success) { + logd("Successfully bound to the satellite service."); + } else { + synchronized (mLock) { + mIsBinding = false; + } + mExponentialBackoff.notifyFailed(); + loge("Error binding to the satellite service. Retrying in " + + mExponentialBackoff.getCurrentDelay() + " ms."); + } + } catch (Exception e) { + synchronized (mLock) { + mIsBinding = false; + } + mExponentialBackoff.notifyFailed(); + loge("Exception binding to the satellite service. Retrying in " + + mExponentialBackoff.getCurrentDelay() + " ms. Exception: " + e); + } + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected void unbindService() { + disconnectSatelliteService(); + mContext.unbindService(mSatelliteServiceConnection); + mSatelliteServiceConnection = null; + } + + private void disconnectSatelliteService() { + // TODO: clean up any listeners and return failed for pending callbacks + mSatelliteService = null; + } + + private class SatelliteServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + logd("onServiceConnected: ComponentName=" + name); + synchronized (mLock) { + mIsBound = true; + mIsBinding = false; + } + mSatelliteService = ISatellite.Stub.asInterface(service); + mExponentialBackoff.stop(); + try { + mSatelliteService.setSatelliteListener(mListener); + } catch (RemoteException e) { + // TODO: Retry setSatelliteListener + logd("setSatelliteListener: RemoteException " + e); + } + mSatelliteController.onSatelliteServiceConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + loge("onServiceDisconnected: Waiting for reconnect."); + synchronized (mLock) { + mIsBinding = false; + } + // Since we are still technically bound, clear the service and wait for reconnect. + disconnectSatelliteService(); + } + + @Override + public void onBindingDied(ComponentName name) { + loge("onBindingDied: Unbinding and rebinding service."); + synchronized (mLock) { + mIsBound = false; + mIsBinding = false; + } + unbindService(); + mExponentialBackoff.start(); + } + } + + /** + * Registers for the satellite provision state changed. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatelliteProvisionStateChanged( + @NonNull Handler h, int what, @Nullable Object obj) { + mSatelliteProvisionStateChangedRegistrants.add(h, what, obj); + } + + /** + * Unregisters for the satellite provision state changed. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) { + mSatelliteProvisionStateChangedRegistrants.remove(h); + } + + /** + * Registers for satellite position info changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatellitePositionInfoChanged( + @NonNull Handler h, int what, @Nullable Object obj) { + mSatellitePositionInfoChangedRegistrants.add(h, what, obj); + } + + /** + * Unregisters for satellite position info changed from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSatellitePositionInfoChanged(@NonNull Handler h) { + mSatellitePositionInfoChangedRegistrants.remove(h); + } + + /** + * Registers for datagram transfer state changed. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForDatagramTransferStateChanged( + @NonNull Handler h, int what, @Nullable Object obj) { + mDatagramTransferStateChangedRegistrants.add(h, what, obj); + } + + /** + * Unregisters for datagram transfer state changed. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForDatagramTransferStateChanged(@NonNull Handler h) { + mDatagramTransferStateChangedRegistrants.remove(h); + } + + /** + * Registers for modem state changed from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatelliteModemStateChanged( + @NonNull Handler h, int what, @Nullable Object obj) { + mSatelliteModemStateChangedRegistrants.add(h, what, obj); + } + + /** + * Unregisters for modem state changed from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSatelliteModemStateChanged(@NonNull Handler h) { + mSatelliteModemStateChangedRegistrants.remove(h); + } + + /** + * Registers for pending datagrams indication from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForPendingDatagrams(@NonNull Handler h, int what, @Nullable Object obj) { + mPendingDatagramsRegistrants.add(h, what, obj); + } + + /** + * Unregisters for pending datagrams indication from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForPendingDatagrams(@NonNull Handler h) { + mPendingDatagramsRegistrants.remove(h); + } + + /** + * Registers for new datagrams received from satellite modem. + * + * @param h Handler for notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSatelliteDatagramsReceived( + @NonNull Handler h, int what, @Nullable Object obj) { + mSatelliteDatagramsReceivedRegistrants.add(h, what, obj); + } + + /** + * Unregisters for new datagrams received from satellite modem. + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSatelliteDatagramsReceived(@NonNull Handler h) { + mSatelliteDatagramsReceivedRegistrants.remove(h); + } + + /** + * Request to enable or disable the satellite service listening mode. + * Listening mode allows the satellite service to listen for incoming pages. + * + * @param enable True to enable satellite listening mode and false to disable. + * @param timeout How long the satellite modem should wait for the next incoming page before + * disabling listening mode. + * @param message The Message to send to result of the operation to. + */ + public void requestSatelliteListeningEnabled(boolean enable, int timeout, + @Nullable Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestSatelliteListeningEnabled(enable, timeout, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestSatelliteListeningEnabled: " + error); + Binder.withCleanCallingIdentity(() -> { + if (message != null) { + sendMessageWithResult(message, null, error); + } + }); + } + }); + } catch (RemoteException e) { + loge("requestSatelliteListeningEnabled: RemoteException " + e); + if (message != null) { + sendMessageWithResult( + message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } + } else { + loge("requestSatelliteListeningEnabled: Satellite service is unavailable."); + if (message != null) { + sendMessageWithResult(message, null, + SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + } + + /** + * Allow cellular modem scanning while satellite mode is on. + * @param enabled {@code true} to enable cellular modem while satellite mode is on + * and {@code false} to disable + * @param message The Message to send to result of the operation to. + */ + public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled, + @Nullable Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.enableCellularModemWhileSatelliteModeIsOn(enabled, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("enableCellularModemWhileSatelliteModeIsOn: " + error); + Binder.withCleanCallingIdentity(() -> { + if (message != null) { + sendMessageWithResult(message, null, error); + } + }); + } + }); + } catch (RemoteException e) { + loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e); + if (message != null) { + sendMessageWithResult( + message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } + } else { + loge("enableCellularModemWhileSatelliteModeIsOn: Satellite service is unavailable."); + if (message != null) { + sendMessageWithResult(message, null, + SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + } + /** + * Request to enable or disable the satellite modem and demo mode. If the satellite modem + * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled, + * this may also re-enable the cellular modem. + * + * @param enableSatellite True to enable the satellite modem and false to disable. + * @param enableDemoMode True to enable demo mode and false to disable. + * @param message The Message to send to result of the operation to. + */ + public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, + @NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestSatelliteEnabled(enableSatellite, enableDemoMode, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("setSatelliteEnabled: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }); + } catch (RemoteException e) { + loge("setSatelliteEnabled: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("setSatelliteEnabled: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Request to get whether the satellite modem is enabled. + * + * @param message The Message to send to result of the operation to. + */ + public void requestIsSatelliteEnabled(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestIsSatelliteEnabled(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestIsSatelliteEnabled: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }, new IBooleanConsumer.Stub() { + @Override + public void accept(boolean result) { + // Convert for compatibility with SatelliteResponse + // TODO: This should just report result instead. + int[] enabled = new int[] {result ? 1 : 0}; + logd("requestIsSatelliteEnabled: " + Arrays.toString(enabled)); + Binder.withCleanCallingIdentity(() -> sendMessageWithResult( + message, enabled, SatelliteManager.SATELLITE_ERROR_NONE)); + } + }); + } catch (RemoteException e) { + loge("requestIsSatelliteEnabled: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("requestIsSatelliteEnabled: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Request to get whether the satellite service is supported on the device. + * + * @param message The Message to send to result of the operation to. + */ + public void requestIsSatelliteSupported(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestIsSatelliteSupported(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestIsSatelliteSupported: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }, new IBooleanConsumer.Stub() { + @Override + public void accept(boolean result) { + logd("requestIsSatelliteSupported: " + result); + Binder.withCleanCallingIdentity(() -> sendMessageWithResult( + message, result, SatelliteManager.SATELLITE_ERROR_NONE)); + } + }); + } catch (RemoteException e) { + loge("requestIsSatelliteSupported: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("requestIsSatelliteSupported: Satellite service is unavailable."); + sendMessageWithResult( + message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Request to get the SatelliteCapabilities of the satellite service. + * + * @param message The Message to send to result of the operation to. + */ + public void requestSatelliteCapabilities(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestSatelliteCapabilities(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestSatelliteCapabilities: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }, new ISatelliteCapabilitiesConsumer.Stub() { + @Override + public void accept(android.telephony.satellite.stub.SatelliteCapabilities + result) { + SatelliteCapabilities capabilities = + SatelliteServiceUtils.fromSatelliteCapabilities(result); + logd("requestSatelliteCapabilities: " + capabilities); + Binder.withCleanCallingIdentity(() -> sendMessageWithResult( + message, capabilities, SatelliteManager.SATELLITE_ERROR_NONE)); + } + }); + } catch (RemoteException e) { + loge("requestSatelliteCapabilities: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("requestSatelliteCapabilities: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * User started pointing to the satellite. + * The satellite service should report the satellite pointing info via + * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves. + * + * @param message The Message to send to result of the operation to. + */ + public void startSendingSatellitePointingInfo(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.startSendingSatellitePointingInfo(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("startSendingSatellitePointingInfo: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }); + } catch (RemoteException e) { + loge("startSendingSatellitePointingInfo: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("startSendingSatellitePointingInfo: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * User stopped pointing to the satellite. + * The satellite service should stop reporting satellite pointing info to the framework. + * + * @param message The Message to send to result of the operation to. + */ + public void stopSendingSatellitePointingInfo(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.stopSendingSatellitePointingInfo(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("stopSendingSatellitePointingInfo: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }); + } catch (RemoteException e) { + loge("stopSendingSatellitePointingInfo: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("stopSendingSatellitePointingInfo: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Provision the device with a satellite provider. + * This is needed if the provider allows dynamic registration. + * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true. + * + * @param token The token to be used as a unique identifier for provisioning with satellite + * gateway. + * @param provisionData Data from the provisioning app that can be used by provisioning server + * @param message The Message to send to result of the operation to. + */ + public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData, + @NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.provisionSatelliteService(token, provisionData, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("provisionSatelliteService: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }); + } catch (RemoteException e) { + loge("provisionSatelliteService: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("provisionSatelliteService: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Deprovision the device with the satellite provider. + * This is needed if the provider allows dynamic registration. + * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false. + * + * @param token The token of the device/subscription to be deprovisioned. + * @param message The Message to send to result of the operation to. + */ + public void deprovisionSatelliteService(@NonNull String token, @NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.deprovisionSatelliteService(token, new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("deprovisionSatelliteService: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }); + } catch (RemoteException e) { + loge("deprovisionSatelliteService: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("deprovisionSatelliteService: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Request to get whether this device is provisioned with a satellite provider. + * + * @param message The Message to send to result of the operation to. + */ + public void requestIsSatelliteProvisioned(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestIsSatelliteProvisioned(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestIsSatelliteProvisioned: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }, new IBooleanConsumer.Stub() { + @Override + public void accept(boolean result) { + // Convert for compatibility with SatelliteResponse + // TODO: This should just report result instead. + int[] provisioned = new int[] {result ? 1 : 0}; + logd("requestIsSatelliteProvisioned: " + Arrays.toString(provisioned)); + Binder.withCleanCallingIdentity(() -> sendMessageWithResult( + message, provisioned, SatelliteManager.SATELLITE_ERROR_NONE)); + } + }); + } catch (RemoteException e) { + loge("requestIsSatelliteProvisioned: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("requestIsSatelliteProvisioned: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Poll the pending datagrams to be received over satellite. + * The satellite service should check if there are any pending datagrams to be received over + * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived. + * + * @param message The Message to send to result of the operation to. + */ + public void pollPendingSatelliteDatagrams(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.pollPendingSatelliteDatagrams(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("pollPendingSatelliteDatagrams: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }); + } catch (RemoteException e) { + loge("pollPendingSatelliteDatagrams: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("pollPendingSatelliteDatagrams: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Send datagram over satellite. + * + * @param datagram Datagram to send in byte format. + * @param isEmergency Whether this is an emergency datagram. + * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in + * full screen mode. + * @param message The Message to send to result of the operation to. + */ + public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency, + boolean needFullScreenPointingUI, @NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.sendSatelliteDatagram( + SatelliteServiceUtils.toSatelliteDatagram(datagram), isEmergency, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("sendSatelliteDatagram: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }); + } catch (RemoteException e) { + loge("sendSatelliteDatagram: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("sendSatelliteDatagram: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Request the current satellite modem state. + * The satellite service should report the current satellite modem state via + * ISatelliteListener#onSatelliteModemStateChanged. + * + * @param message The Message to send to result of the operation to. + */ + public void requestSatelliteModemState(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestSatelliteModemState(new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestSatelliteModemState: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }, new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + // Convert SatelliteModemState from service to frameworks definition. + int modemState = SatelliteServiceUtils.fromSatelliteModemState(result); + logd("requestSatelliteModemState: " + modemState); + Binder.withCleanCallingIdentity(() -> sendMessageWithResult( + message, modemState, SatelliteManager.SATELLITE_ERROR_NONE)); + } + }); + } catch (RemoteException e) { + loge("requestSatelliteModemState: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("requestSatelliteModemState: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Request to get whether satellite communication is allowed for the current location. + * + * @param message The Message to send to result of the operation to. + */ + public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestIsSatelliteCommunicationAllowedForCurrentLocation( + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: " + + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }, new IBooleanConsumer.Stub() { + @Override + public void accept(boolean result) { + logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: " + + result); + Binder.withCleanCallingIdentity(() -> sendMessageWithResult( + message, result, SatelliteManager.SATELLITE_ERROR_NONE)); + } + }); + } catch (RemoteException e) { + loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: RemoteException " + + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: " + + "Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + /** + * Request to get the time after which the satellite will be visible. This is an int + * representing the duration in seconds after which the satellite will be visible. + * This will return 0 if the satellite is currently visible. + * + * @param message The Message to send to result of the operation to. + */ + public void requestTimeForNextSatelliteVisibility(@NonNull Message message) { + if (mSatelliteService != null) { + try { + mSatelliteService.requestTimeForNextSatelliteVisibility( + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + int error = SatelliteServiceUtils.fromSatelliteError(result); + logd("requestTimeForNextSatelliteVisibility: " + error); + Binder.withCleanCallingIdentity(() -> + sendMessageWithResult(message, null, error)); + } + }, new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + // Convert for compatibility with SatelliteResponse + // TODO: This should just report result instead. + int[] time = new int[] {result}; + logd("requestTimeForNextSatelliteVisibility: " + + Arrays.toString(time)); + Binder.withCleanCallingIdentity(() -> sendMessageWithResult( + message, time, SatelliteManager.SATELLITE_ERROR_NONE)); + } + }); + } catch (RemoteException e) { + loge("requestTimeForNextSatelliteVisibility: RemoteException " + e); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR); + } + } else { + loge("requestTimeForNextSatelliteVisibility: Satellite service is unavailable."); + sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE); + } + } + + public boolean isSatelliteServiceSupported() { + return mIsSatelliteServiceSupported; + } + + /** + * This API can be used by only CTS to update satellite vendor service package name. + * + * @param servicePackageName The package name of the satellite vendor service. + * @return {@code true} if the satellite vendor service is set successfully, + * {@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; + } + + logd("setSatelliteServicePackageName: config_satellite_service_package is " + + "updated, new packageName=" + servicePackageName); + mExponentialBackoff.stop(); + if (mSatelliteServiceConnection != null) { + synchronized (mLock) { + mIsBound = false; + mIsBinding = false; + } + unbindService(); + } + + if (servicePackageName == null || servicePackageName.equals("null")) { + mVendorSatellitePackageName = ""; + } else { + mVendorSatellitePackageName = servicePackageName; + } + mIsSatelliteServiceSupported = getSatelliteServiceSupport(); + bindService(); + mExponentialBackoff.start(); + + return true; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected static void sendMessageWithResult(@NonNull Message message, @Nullable Object result, + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = error == SatelliteManager.SATELLITE_ERROR_NONE + ? null : new SatelliteException(error); + AsyncResult.forMessage(message, result, exception); + 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); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java new file mode 100644 index 0000000000000000000000000000000000000000..25657b3d7c7ae3e6b461f76a00dc305e3b2c2058 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java @@ -0,0 +1,410 @@ +/* + * 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.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED; +import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR_NONE; + +import android.annotation.NonNull; +import android.os.AsyncResult; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ResultReceiver; +import android.provider.DeviceConfig; +import android.telecom.Call; +import android.telecom.Connection; +import android.telephony.Rlog; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; +import android.telephony.ims.RegistrationManager; +import android.telephony.satellite.ISatelliteProvisionStateCallback; +import android.util.Pair; + +import com.android.ims.ImsException; +import com.android.ims.ImsManager; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.metrics.SatelliteStats; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * This module is responsible for monitoring the cellular service state and IMS registration state + * during an emergency call and notify Dialer when Telephony is not able to find any network and + * the call likely will not get connected so that Dialer will prompt the user if they would like to + * switch to satellite messaging. + */ +public class SatelliteSOSMessageRecommender extends Handler { + private static final String TAG = "SatelliteSOSMessageRecommender"; + + /** + * Device config for the timeout duration in milliseconds to determine whether to recommend + * Dialer to show the SOS button to users. + *

+ * The timer is started when there is an ongoing emergency call, and the IMS is not registered, + * and cellular service is not available. When the timer expires, SatelliteSOSMessageRecommender + * will send the event EVENT_DISPLAY_SOS_MESSAGE to Dialer, which will then prompt user to + * switch to using satellite SOS messaging. + */ + public static final String EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = + "emergency_call_to_sos_msg_hysteresis_timeout_millis"; + /** + * The default value of {@link #EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS} when it is + * not provided in the device config. + */ + public static final long DEFAULT_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 20000; + + private static final int EVENT_EMERGENCY_CALL_STARTED = 1; + protected static final int EVENT_CELLULAR_SERVICE_STATE_CHANGED = 2; + private static final int EVENT_IMS_REGISTRATION_STATE_CHANGED = 3; + protected static final int EVENT_TIME_OUT = 4; + private static final int EVENT_SATELLITE_PROVISIONED_STATE_CHANGED = 5; + private static final int EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED = 6; + + @NonNull + private final SatelliteController mSatelliteController; + private ImsManager mImsManager; + + private Connection mEmergencyConnection = null; + /* The phone used for emergency call */ + private Phone mPhone = null; + private final ISatelliteProvisionStateCallback mISatelliteProvisionStateCallback; + @ServiceState.RegState + private AtomicInteger mCellularServiceState = new AtomicInteger(); + private AtomicBoolean mIsImsRegistered = new AtomicBoolean(); + private AtomicBoolean mIsSatelliteAllowedInCurrentLocation = new AtomicBoolean(); + private final ResultReceiver mReceiverForRequestIsSatelliteAllowedForCurrentLocation; + private final long mTimeoutMillis; + protected int mCountOfTimerStarted = 0; + + private RegistrationManager.RegistrationCallback mImsRegistrationCallback = + new RegistrationManager.RegistrationCallback() { + @Override + public void onRegistered(ImsRegistrationAttributes attributes) { + sendMessage(obtainMessage(EVENT_IMS_REGISTRATION_STATE_CHANGED, true)); + } + + @Override + public void onUnregistered(ImsReasonInfo info) { + sendMessage(obtainMessage(EVENT_IMS_REGISTRATION_STATE_CHANGED, false)); + } + }; + + /** + * Create an instance of SatelliteSOSMessageRecommender. + * + * @param looper The looper used with the handler of this class. + */ + public SatelliteSOSMessageRecommender(@NonNull Looper looper) { + this(looper, SatelliteController.getInstance(), null, + getEmergencyCallToSosMsgHysteresisTimeoutMillis()); + } + + /** + * Create an instance of SatelliteSOSMessageRecommender. This constructor should be used in + * only unit tests. + * + * @param looper The looper used with the handler of this class. + * @param satelliteController The SatelliteController singleton instance. + * @param imsManager The ImsManager instance associated with the phone, which is used for making + * the emergency call. This argument is not null only in unit tests. + * @param timeoutMillis The timeout duration of the timer. + */ + @VisibleForTesting + protected SatelliteSOSMessageRecommender(@NonNull Looper looper, + @NonNull SatelliteController satelliteController, ImsManager imsManager, + long timeoutMillis) { + super(looper); + mSatelliteController = satelliteController; + mImsManager = imsManager; + mTimeoutMillis = timeoutMillis; + mISatelliteProvisionStateCallback = new ISatelliteProvisionStateCallback.Stub() { + @Override + public void onSatelliteProvisionStateChanged(boolean provisioned) { + logd("onSatelliteProvisionStateChanged: provisioned=" + provisioned); + sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned)); + } + }; + mReceiverForRequestIsSatelliteAllowedForCurrentLocation = new ResultReceiver(this) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) { + boolean isSatelliteCommunicationAllowed = + resultData.getBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED); + mIsSatelliteAllowedInCurrentLocation.set(isSatelliteCommunicationAllowed); + if (!isSatelliteCommunicationAllowed) { + logd("Satellite is not allowed for current location."); + cleanUpResources(); + } + } else { + loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist."); + mIsSatelliteAllowedInCurrentLocation.set(false); + cleanUpResources(); + } + } else { + loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() resultCode=" + + resultCode); + mIsSatelliteAllowedInCurrentLocation.set(false); + cleanUpResources(); + } + } + }; + } + + @Override + public void handleMessage(@NonNull Message msg) { + switch (msg.what) { + case EVENT_EMERGENCY_CALL_STARTED: + handleEmergencyCallStartedEvent((Pair) msg.obj); + break; + case EVENT_TIME_OUT: + handleTimeoutEvent(); + break; + case EVENT_SATELLITE_PROVISIONED_STATE_CHANGED: + handleSatelliteProvisionStateChangedEvent((boolean) msg.obj); + break; + case EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED: + handleEmergencyCallConnectionStateChangedEvent((Pair) msg.obj); + break; + case EVENT_IMS_REGISTRATION_STATE_CHANGED: + handleImsRegistrationStateChangedEvent((boolean) msg.obj); + break; + case EVENT_CELLULAR_SERVICE_STATE_CHANGED: + AsyncResult ar = (AsyncResult) msg.obj; + handleCellularServiceStateChangedEvent((ServiceState) ar.result); + break; + default: + logd("handleMessage: unexpected message code: " + msg.what); + break; + } + } + + /** + * Inform SatelliteSOSMessageRecommender that an emergency call has just started. + * + * @param connection The connection created by TelephonyConnectionService for the emergency + * call. + * @param phone The phone used for the emergency call. + */ + public void onEmergencyCallStarted(@NonNull Connection connection, @NonNull Phone phone) { + if (!mSatelliteController.isSatelliteSupported()) { + logd("onEmergencyCallStarted: satellite is not supported"); + return; + } + Pair argument = new Pair<>(connection, phone); + sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_STARTED, argument)); + } + + /** + * Inform SatelliteSOSMessageRecommender that the state of the emergency call connection has + * changed. + * + * @param callId The ID of the emergency call. + * @param state The connection state of the emergency call. + */ + public void onEmergencyCallConnectionStateChanged( + String callId, @Connection.ConnectionState int state) { + Pair argument = new Pair<>(callId, state); + sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED, argument)); + } + + private void handleEmergencyCallStartedEvent(@NonNull Pair arg) { + mSatelliteController.requestIsSatelliteCommunicationAllowedForCurrentLocation( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + mReceiverForRequestIsSatelliteAllowedForCurrentLocation); + if (mPhone != null) { + logd("handleEmergencyCallStartedEvent: new emergency call started while there is " + + " an ongoing call"); + unregisterForInterestedStateChangedEvents(mPhone); + } + mPhone = arg.second; + mEmergencyConnection = arg.first; + mCellularServiceState.set(mPhone.getServiceState().getState()); + mIsImsRegistered.set(mPhone.isImsRegistered()); + handleStateChangedEventForHysteresisTimer(); + registerForInterestedStateChangedEvents(mPhone); + } + + private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) { + if (!provisioned) { + cleanUpResources(); + } + } + + private void handleTimeoutEvent() { + boolean isDialerNotified = false; + if (!mIsImsRegistered.get() && !isCellularAvailable() + && mIsSatelliteAllowedInCurrentLocation.get() + && mSatelliteController.isSatelliteProvisioned() + && shouldTrackCall(mEmergencyConnection.getState())) { + logd("handleTimeoutEvent: Sending EVENT_DISPLAY_SOS_MESSAGE to Dialer..."); + mEmergencyConnection.sendConnectionEvent(Call.EVENT_DISPLAY_SOS_MESSAGE, null); + isDialerNotified = true; + } + reportEsosRecommenderDecision(isDialerNotified); + cleanUpResources(); + } + + private void handleEmergencyCallConnectionStateChangedEvent( + @NonNull Pair arg) { + if (mEmergencyConnection == null) { + // Either the call was not created or the timer already timed out. + return; + } + + String callId = arg.first; + int state = arg.second; + if (!mEmergencyConnection.getTelecomCallId().equals(callId)) { + loge("handleEmergencyCallConnectionStateChangedEvent: unexpected state changed event " + + ", mEmergencyConnection=" + mEmergencyConnection + ", callId=" + callId + + ", state=" + state); + /** + * TelephonyConnectionService sent us a connection state changed event for a call that + * we're not tracking. There must be some unexpected things happened in + * TelephonyConnectionService. Thus, we need to clean up the resources. + */ + cleanUpResources(); + return; + } + + if (!shouldTrackCall(state)) { + reportEsosRecommenderDecision(false); + cleanUpResources(); + } + } + + private void handleImsRegistrationStateChangedEvent(boolean registered) { + if (registered != mIsImsRegistered.get()) { + mIsImsRegistered.set(registered); + handleStateChangedEventForHysteresisTimer(); + } + } + + private void handleCellularServiceStateChangedEvent(@NonNull ServiceState serviceState) { + int state = serviceState.getState(); + if (mCellularServiceState.get() != state) { + mCellularServiceState.set(state); + handleStateChangedEventForHysteresisTimer(); + } + } + + private void reportEsosRecommenderDecision(boolean isDialerNotified) { + SatelliteStats.getInstance().onSatelliteSosMessageRecommender( + new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder() + .setDisplaySosMessageSent(isDialerNotified) + .setCountOfTimerStarted(mCountOfTimerStarted) + .setImsRegistered(mIsImsRegistered.get()) + .setCellularServiceState(mCellularServiceState.get()) + .build()); + } + + private void cleanUpResources() { + stopTimer(); + if (mPhone != null) { + unregisterForInterestedStateChangedEvents(mPhone); + mPhone = null; + } + mEmergencyConnection = null; + mCountOfTimerStarted = 0; + } + + private void registerForInterestedStateChangedEvents(@NonNull Phone phone) { + mSatelliteController.registerForSatelliteProvisionStateChanged( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback); + phone.registerForServiceStateChanged(this, EVENT_CELLULAR_SERVICE_STATE_CHANGED, null); + registerForImsRegistrationStateChanged(phone); + } + + private void registerForImsRegistrationStateChanged(@NonNull Phone phone) { + ImsManager imsManager = (mImsManager != null) ? mImsManager : ImsManager.getInstance( + phone.getContext(), phone.getPhoneId()); + try { + imsManager.addRegistrationCallback(mImsRegistrationCallback, this::post); + } catch (ImsException ex) { + loge("registerForImsRegistrationStateChanged: ex=" + ex); + } + } + + private void unregisterForInterestedStateChangedEvents(@NonNull Phone phone) { + mSatelliteController.unregisterForSatelliteProvisionStateChanged( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback); + phone.unregisterForServiceStateChanged(this); + unregisterForImsRegistrationStateChanged(phone); + } + + private void unregisterForImsRegistrationStateChanged(@NonNull Phone phone) { + ImsManager imsManager = (mImsManager != null) ? mImsManager : ImsManager.getInstance( + phone.getContext(), phone.getPhoneId()); + imsManager.removeRegistrationListener(mImsRegistrationCallback); + } + + private boolean isCellularAvailable() { + return (mCellularServiceState.get() == ServiceState.STATE_IN_SERVICE + || mCellularServiceState.get() == ServiceState.STATE_EMERGENCY_ONLY); + } + + private void handleStateChangedEventForHysteresisTimer() { + if (!mIsImsRegistered.get() && !isCellularAvailable()) { + startTimer(); + } else { + stopTimer(); + } + } + + private void startTimer() { + if (hasMessages(EVENT_TIME_OUT)) { + return; + } + sendMessageDelayed(obtainMessage(EVENT_TIME_OUT), mTimeoutMillis); + mCountOfTimerStarted++; + } + + private void stopTimer() { + removeMessages(EVENT_TIME_OUT); + } + + private static long getEmergencyCallToSosMsgHysteresisTimeoutMillis() { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY, + EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS, + DEFAULT_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + } + + private boolean shouldTrackCall(int connectionState) { + /** + * An active connection state means both parties are connected to the call and can actively + * communicate. A disconnected connection state means the emergency call has ended. In both + * cases, we don't need to track the call anymore. + */ + return (connectionState != Connection.STATE_ACTIVE + && connectionState != Connection.STATE_DISCONNECTED); + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..f11ca661de707344a5770ba5c2ce298f5790bd58 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java @@ -0,0 +1,285 @@ +/* + * 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.annotation.Nullable; +import android.content.Context; +import android.os.AsyncResult; +import android.os.Binder; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.telephony.satellite.AntennaPosition; +import android.telephony.satellite.PointingInfo; +import android.telephony.satellite.SatelliteCapabilities; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; +import android.telephony.satellite.stub.NTRadioTechnology; +import android.telephony.satellite.stub.SatelliteError; +import android.telephony.satellite.stub.SatelliteModemState; + +import com.android.internal.telephony.CommandException; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.RILUtils; +import com.android.internal.telephony.subscription.SubscriptionManagerService; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Utils class for satellite service <-> framework conversions + */ +public class SatelliteServiceUtils { + private static final String TAG = "SatelliteServiceUtils"; + + /** + * Convert radio technology from service definition to framework definition. + * @param radioTechnology The NTRadioTechnology from the satellite service. + * @return The converted NTRadioTechnology for the framework. + */ + @SatelliteManager.NTRadioTechnology + public static int fromSatelliteRadioTechnology(int radioTechnology) { + switch (radioTechnology) { + case NTRadioTechnology.NB_IOT_NTN: + return SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN; + case NTRadioTechnology.NR_NTN: + return SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN; + case NTRadioTechnology.EMTC_NTN: + return SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN; + case NTRadioTechnology.PROPRIETARY: + return SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY; + default: + loge("Received invalid radio technology: " + radioTechnology); + return SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN; + } + } + + /** + * Convert satellite error from service definition to framework definition. + * @param error The SatelliteError from the satellite service. + * @return The converted SatelliteError for the framework. + */ + @SatelliteManager.SatelliteError public static int fromSatelliteError(int error) { + switch (error) { + case SatelliteError.ERROR_NONE: + return SatelliteManager.SATELLITE_ERROR_NONE; + case SatelliteError.SATELLITE_ERROR: + return SatelliteManager.SATELLITE_ERROR; + case SatelliteError.SERVER_ERROR: + return SatelliteManager.SATELLITE_SERVER_ERROR; + case SatelliteError.SERVICE_ERROR: + return SatelliteManager.SATELLITE_SERVICE_ERROR; + case SatelliteError.MODEM_ERROR: + return SatelliteManager.SATELLITE_MODEM_ERROR; + case SatelliteError.NETWORK_ERROR: + return SatelliteManager.SATELLITE_NETWORK_ERROR; + case SatelliteError.INVALID_TELEPHONY_STATE: + return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + case SatelliteError.INVALID_MODEM_STATE: + return SatelliteManager.SATELLITE_INVALID_MODEM_STATE; + case SatelliteError.INVALID_ARGUMENTS: + return SatelliteManager.SATELLITE_INVALID_ARGUMENTS; + case SatelliteError.REQUEST_FAILED: + return SatelliteManager.SATELLITE_REQUEST_FAILED; + case SatelliteError.RADIO_NOT_AVAILABLE: + return SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE; + case SatelliteError.REQUEST_NOT_SUPPORTED: + return SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED; + case SatelliteError.NO_RESOURCES: + return SatelliteManager.SATELLITE_NO_RESOURCES; + case SatelliteError.SERVICE_NOT_PROVISIONED: + return SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED; + case SatelliteError.SERVICE_PROVISION_IN_PROGRESS: + return SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS; + case SatelliteError.REQUEST_ABORTED: + return SatelliteManager.SATELLITE_REQUEST_ABORTED; + case SatelliteError.SATELLITE_ACCESS_BARRED: + return SatelliteManager.SATELLITE_ACCESS_BARRED; + case SatelliteError.NETWORK_TIMEOUT: + return SatelliteManager.SATELLITE_NETWORK_TIMEOUT; + case SatelliteError.SATELLITE_NOT_REACHABLE: + return SatelliteManager.SATELLITE_NOT_REACHABLE; + case SatelliteError.NOT_AUTHORIZED: + return SatelliteManager.SATELLITE_NOT_AUTHORIZED; + } + loge("Received invalid satellite service error: " + error); + return SatelliteManager.SATELLITE_SERVICE_ERROR; + } + + /** + * Convert satellite modem state from service definition to framework definition. + * @param modemState The SatelliteModemState from the satellite service. + * @return The converted SatelliteModemState for the framework. + */ + @SatelliteManager.SatelliteModemState + public static int fromSatelliteModemState(int modemState) { + switch (modemState) { + case SatelliteModemState.SATELLITE_MODEM_STATE_IDLE: + return SatelliteManager.SATELLITE_MODEM_STATE_IDLE; + case SatelliteModemState.SATELLITE_MODEM_STATE_LISTENING: + return SatelliteManager.SATELLITE_MODEM_STATE_LISTENING; + case SatelliteModemState.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING: + return SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING; + case SatelliteModemState.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING: + return SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING; + case SatelliteModemState.SATELLITE_MODEM_STATE_OFF: + return SatelliteManager.SATELLITE_MODEM_STATE_OFF; + case SatelliteModemState.SATELLITE_MODEM_STATE_UNAVAILABLE: + return SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE; + default: + loge("Received invalid modem state: " + modemState); + return SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN; + } + } + + /** + * Convert SatelliteCapabilities from service definition to framework definition. + * @param capabilities The SatelliteCapabilities from the satellite service. + * @return The converted SatelliteCapabilities for the framework. + */ + @Nullable public static SatelliteCapabilities fromSatelliteCapabilities( + @Nullable android.telephony.satellite.stub.SatelliteCapabilities capabilities) { + if (capabilities == null) return null; + int[] radioTechnologies = capabilities.supportedRadioTechnologies == null + ? new int[0] : capabilities.supportedRadioTechnologies; + + Map antennaPositionMap = new HashMap<>(); + int[] antennaPositionKeys = capabilities.antennaPositionKeys; + AntennaPosition[] antennaPositionValues = capabilities.antennaPositionValues; + if (antennaPositionKeys != null && antennaPositionValues != null && + antennaPositionKeys.length == antennaPositionValues.length) { + for(int i = 0; i < antennaPositionKeys.length; i++) { + antennaPositionMap.put(antennaPositionKeys[i], antennaPositionValues[i]); + } + } + + return new SatelliteCapabilities( + Arrays.stream(radioTechnologies) + .map(SatelliteServiceUtils::fromSatelliteRadioTechnology) + .boxed().collect(Collectors.toSet()), + capabilities.isPointingRequired, capabilities.maxBytesPerOutgoingDatagram, + antennaPositionMap); + } + + /** + * Convert PointingInfo from service definition to framework definition. + * @param pointingInfo The PointingInfo from the satellite service. + * @return The converted PointingInfo for the framework. + */ + @Nullable public static PointingInfo fromPointingInfo( + android.telephony.satellite.stub.PointingInfo pointingInfo) { + if (pointingInfo == null) return null; + return new PointingInfo(pointingInfo.satelliteAzimuth, pointingInfo.satelliteElevation); + } + + /** + * Convert SatelliteDatagram from service definition to framework definition. + * @param datagram The SatelliteDatagram from the satellite service. + * @return The converted SatelliteDatagram for the framework. + */ + @Nullable public static SatelliteDatagram fromSatelliteDatagram( + android.telephony.satellite.stub.SatelliteDatagram datagram) { + if (datagram == null) return null; + byte[] data = datagram.data == null ? new byte[0] : datagram.data; + return new SatelliteDatagram(data); + } + + /** + * Convert SatelliteDatagram from framework definition to service definition. + * @param datagram The SatelliteDatagram from the framework. + * @return The converted SatelliteDatagram for the satellite service. + */ + @Nullable public static android.telephony.satellite.stub.SatelliteDatagram toSatelliteDatagram( + @Nullable SatelliteDatagram datagram) { + android.telephony.satellite.stub.SatelliteDatagram converted = + new android.telephony.satellite.stub.SatelliteDatagram(); + converted.data = datagram.getSatelliteDatagram(); + return converted; + } + + /** + * Get the {@link SatelliteManager.SatelliteError} from the provided result. + * + * @param ar AsyncResult used to determine the error code. + * @param caller The satellite request. + * + * @return The {@link SatelliteManager.SatelliteError} error code from the request. + */ + @SatelliteManager.SatelliteError public static int getSatelliteError(@NonNull AsyncResult ar, + @NonNull String caller) { + int errorCode; + if (ar.exception == null) { + errorCode = SatelliteManager.SATELLITE_ERROR_NONE; + } else { + errorCode = SatelliteManager.SATELLITE_ERROR; + if (ar.exception instanceof CommandException) { + CommandException.Error error = ((CommandException) ar.exception).getCommandError(); + errorCode = RILUtils.convertToSatelliteError(error); + loge(caller + " CommandException: " + ar.exception); + } else if (ar.exception instanceof SatelliteManager.SatelliteException) { + errorCode = ((SatelliteManager.SatelliteException) ar.exception).getErrorCode(); + loge(caller + " SatelliteException: " + ar.exception); + } else { + loge(caller + " unknown exception: " + ar.exception); + } + } + logd(caller + " error: " + errorCode); + return errorCode; + } + + /** + * Get valid subscription id for satellite communication. + * + * @param subId The subscription id. + * @return input subId if the subscription is active else return default subscription id. + */ + public static int getValidSatelliteSubId(int subId, @NonNull Context context) { + final long identity = Binder.clearCallingIdentity(); + try { + boolean isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId, + context.getOpPackageName(), context.getAttributionTag()); + + if (isActive) { + return subId; + } + } finally { + Binder.restoreCallingIdentity(identity); + } + logd("getValidSatelliteSubId: use DEFAULT_SUBSCRIPTION_ID for subId=" + subId); + return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + } + + /** + * Return phone associated with phoneId 0. + * + * @return phone associated with phoneId 0 or {@code null} if it doesn't exist. + */ + public static @Nullable Phone getPhone() { + return PhoneFactory.getPhone(0); + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } + + private static void loge(@NonNull String log) { + Rlog.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java new file mode 100644 index 0000000000000000000000000000000000000000..36ad2503d0b6a455bac4687dd011ca1c1978f5ef --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java @@ -0,0 +1,742 @@ +/* + * 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.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.provider.DeviceConfig; +import android.telephony.Rlog; +import android.telephony.satellite.ISatelliteStateCallback; +import android.telephony.satellite.SatelliteManager; +import android.telephony.satellite.stub.ISatelliteGateway; +import android.telephony.satellite.stub.SatelliteGatewayService; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.ExponentialBackoff; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This module is responsible for managing session state transition and inform listeners of modem + * state changed events accordingly. + */ +public class SatelliteSessionController extends StateMachine { + private static final String TAG = "SatelliteSessionController"; + private static final boolean DBG = true; + private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; + private static final boolean DEBUG = !"user".equals(Build.TYPE); + + /** + * The time duration in millis that the satellite will stay at listening mode to wait for the + * next incoming page before disabling listening mode when transitioning from sending mode. + */ + public static final String SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS = + "satellite_stay_at_listening_from_sending_millis"; + /** + * The default value of {@link #SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS}. + */ + public static final long DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS = 180000; + /** + * The time duration in millis that the satellite will stay at listening mode to wait for the + * next incoming page before disabling listening mode when transitioning from receiving mode. + */ + public static final String SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS = + "satellite_stay_at_listening_from_receiving_millis"; + /** + * The default value of {@link #SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS} + */ + public static final long DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS = 30000; + /** + * The default value of {@link #SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS}, + * and {@link #SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS} for demo mode + */ + public static final long DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS = 3000; + + private static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 1; + private static final int EVENT_LISTENING_TIMER_TIMEOUT = 2; + private static final int EVENT_SATELLITE_ENABLED_STATE_CHANGED = 3; + + 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; + @NonNull private final ExponentialBackoff mExponentialBackoff; + @NonNull private final Object mLock = new Object(); + @Nullable + private ISatelliteGateway mSatelliteGatewayService; + private String mSatelliteGatewayServicePackageName = ""; + @Nullable private SatelliteGatewayServiceConnection mSatelliteGatewayServiceConnection; + private boolean mIsBound; + private boolean mIsBinding; + + @NonNull private static SatelliteSessionController sInstance; + + @NonNull private final Context mContext; + @NonNull private final SatelliteModemInterface mSatelliteModemInterface; + @NonNull private final UnavailableState mUnavailableState = new UnavailableState(); + @NonNull private final PowerOffState mPowerOffState = new PowerOffState(); + @NonNull private final IdleState mIdleState = new IdleState(); + @NonNull private final TransferringState mTransferringState = new TransferringState(); + @NonNull private final ListeningState mListeningState = new ListeningState(); + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected AtomicBoolean mIsSendingTriggeredDuringTransferringState; + private long mSatelliteStayAtListeningFromSendingMillis; + private long mSatelliteStayAtListeningFromReceivingMillis; + private final ConcurrentHashMap mListeners; + @SatelliteManager.SatelliteModemState private int mCurrentState; + final boolean mIsSatelliteSupported; + private boolean mIsDemoMode = false; + + /** + * @return The singleton instance of SatelliteSessionController. + */ + public static SatelliteSessionController getInstance() { + if (sInstance == null) { + Log.e(TAG, "SatelliteSessionController was not yet initialized."); + } + return sInstance; + } + + /** + * Create the SatelliteSessionController singleton instance. + * + * @param context The Context for the SatelliteSessionController. + * @param looper The looper associated with the handler of this class. + * @param isSatelliteSupported Whether satellite is supported on the device. + * @return The singleton instance of SatelliteSessionController. + */ + public static SatelliteSessionController make( + @NonNull Context context, @NonNull Looper looper, boolean isSatelliteSupported) { + if (sInstance == null) { + sInstance = new SatelliteSessionController(context, looper, isSatelliteSupported, + SatelliteModemInterface.getInstance(), + getSatelliteStayAtListeningFromSendingMillis(), + getSatelliteStayAtListeningFromReceivingMillis()); + } else { + if (isSatelliteSupported != sInstance.mIsSatelliteSupported) { + Rlog.e(TAG, "New satellite support state " + isSatelliteSupported + + " is different from existing state " + sInstance.mIsSatelliteSupported + + ". Ignore the new state."); + } + } + return sInstance; + } + + /** + * Create a SatelliteSessionController to manage satellite session. + * + * @param context The Context for the SatelliteSessionController. + * @param looper The looper associated with the handler of this class. + * @param isSatelliteSupported Whether satellite is supported on the device. + * @param satelliteModemInterface The singleton of SatelliteModemInterface. + * @param satelliteStayAtListeningFromSendingMillis The duration to stay at listening mode when + * transitioning from sending mode. + * @param satelliteStayAtListeningFromReceivingMillis The duration to stay at listening mode + * when transitioning from receiving mode. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected SatelliteSessionController(@NonNull Context context, @NonNull Looper looper, + boolean isSatelliteSupported, + @NonNull SatelliteModemInterface satelliteModemInterface, + long satelliteStayAtListeningFromSendingMillis, + long satelliteStayAtListeningFromReceivingMillis) { + super(TAG, looper); + + mContext = context; + mSatelliteModemInterface = satelliteModemInterface; + mSatelliteStayAtListeningFromSendingMillis = satelliteStayAtListeningFromSendingMillis; + mSatelliteStayAtListeningFromReceivingMillis = satelliteStayAtListeningFromReceivingMillis; + mListeners = new ConcurrentHashMap<>(); + mIsSendingTriggeredDuringTransferringState = new AtomicBoolean(false); + mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN; + mIsSatelliteSupported = isSatelliteSupported; + mExponentialBackoff = new ExponentialBackoff(REBIND_INITIAL_DELAY, REBIND_MAXIMUM_DELAY, + REBIND_MULTIPLIER, looper, () -> { + synchronized (mLock) { + if ((mIsBound && mSatelliteGatewayService != null) || mIsBinding) { + return; + } + } + if (mSatelliteGatewayServiceConnection != null) { + synchronized (mLock) { + mIsBound = false; + mIsBinding = false; + } + unbindService(); + } + bindService(); + }); + + addState(mUnavailableState); + addState(mPowerOffState); + addState(mIdleState); + addState(mTransferringState); + addState(mListeningState, mTransferringState); + setInitialState(isSatelliteSupported); + start(); + } + + /** + * {@link DatagramController} uses this function to notify {@link SatelliteSessionController} + * that its datagram transfer state has changed. + * + * @param sendState The current datagram send state of {@link DatagramController}. + * @param receiveState The current datagram receive state of {@link DatagramController}. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void onDatagramTransferStateChanged( + @SatelliteManager.SatelliteDatagramTransferState int sendState, + @SatelliteManager.SatelliteDatagramTransferState int receiveState) { + sendMessage(EVENT_DATAGRAM_TRANSFER_STATE_CHANGED, + new DatagramTransferState(sendState, receiveState)); + if (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING) { + mIsSendingTriggeredDuringTransferringState.set(true); + } + } + + /** + * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController} + * that the satellite enabled state has changed. + * + * @param enabled {@code true} means enabled and {@code false} means disabled. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void onSatelliteEnabledStateChanged(boolean enabled) { + sendMessage(EVENT_SATELLITE_ENABLED_STATE_CHANGED, enabled); + } + + /** + * Registers for modem state changed from satellite modem. + * + * @param callback The callback to handle the satellite modem state changed event. + */ + public void registerForSatelliteModemStateChanged(@NonNull ISatelliteStateCallback callback) { + try { + callback.onSatelliteModemStateChanged(mCurrentState); + mListeners.put(callback.asBinder(), callback); + } catch (RemoteException ex) { + loge("registerForSatelliteModemStateChanged: Got RemoteException ex=" + ex); + } + } + + /** + * Unregisters for modem state changed from satellite modem. + * If callback was not registered before, the request will be ignored. + * + * @param callback The callback that was passed to + * {@link #registerForSatelliteModemStateChanged(ISatelliteStateCallback)}. + */ + public void unregisterForSatelliteModemStateChanged(@NonNull ISatelliteStateCallback callback) { + mListeners.remove(callback.asBinder()); + } + + /** + * This API can be used by only CTS to update the timeout duration in milliseconds that + * satellite should stay at listening mode to wait for the next incoming page before disabling + * listening mode. + * + * @param timeoutMillis The timeout duration in millisecond. + * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise. + */ + boolean setSatelliteListeningTimeoutDuration(long timeoutMillis) { + if (!isMockModemAllowed()) { + loge("Updating listening timeout duration is not allowed"); + return false; + } + + logd("setSatelliteListeningTimeoutDuration: timeoutMillis=" + timeoutMillis); + if (timeoutMillis == 0) { + mSatelliteStayAtListeningFromSendingMillis = + getSatelliteStayAtListeningFromSendingMillis(); + mSatelliteStayAtListeningFromReceivingMillis = + getSatelliteStayAtListeningFromReceivingMillis(); + } else { + mSatelliteStayAtListeningFromSendingMillis = timeoutMillis; + mSatelliteStayAtListeningFromReceivingMillis = timeoutMillis; + } + + return true; + } + + /** + * This API can be used by only CTS to update satellite gateway service package name. + * + * @param servicePackageName The package name of the satellite gateway service. + * @return {@code true} if the satellite gateway service is set successfully, + * {@code false} otherwise. + */ + boolean setSatelliteGatewayServicePackageName(@Nullable String servicePackageName) { + if (!isMockModemAllowed()) { + loge("setSatelliteGatewayServicePackageName: modifying satellite gateway service " + + "package name is not allowed"); + return false; + } + + logd("setSatelliteGatewayServicePackageName: config_satellite_gateway_service_package is " + + "updated, new packageName=" + servicePackageName); + + if (servicePackageName == null || servicePackageName.equals("null")) { + mSatelliteGatewayServicePackageName = ""; + } else { + mSatelliteGatewayServicePackageName = servicePackageName; + } + + if (mSatelliteGatewayServiceConnection != null) { + synchronized (mLock) { + mIsBound = false; + mIsBinding = false; + } + unbindService(); + bindService(); + } + return true; + } + /** + * Adjusts listening timeout duration when demo mode is on + * + * @param isDemoMode {@code true} : The listening timeout durations will be set to + * {@link #DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS} + * {@code false} : The listening timeout durations will be restored to + * production mode + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setDemoMode(boolean isDemoMode) { + mIsDemoMode = isDemoMode; + } + + private boolean isDemoMode() { + return mIsDemoMode; + } + + private static class DatagramTransferState { + @SatelliteManager.SatelliteDatagramTransferState public int sendState; + @SatelliteManager.SatelliteDatagramTransferState public int receiveState; + + DatagramTransferState(@SatelliteManager.SatelliteDatagramTransferState int sendState, + @SatelliteManager.SatelliteDatagramTransferState int receiveState) { + this.sendState = sendState; + this.receiveState = receiveState; + } + } + + private class UnavailableState extends State { + @Override + public void enter() { + if (DBG) logd("Entering UnavailableState"); + mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE; + } + + @Override + public boolean processMessage(Message msg) { + loge("UnavailableState: receive msg " + getWhatToString(msg.what) + " unexpectedly"); + return HANDLED; + } + } + + private class PowerOffState extends State { + @Override + public void enter() { + if (DBG) logd("Entering PowerOffState"); + + mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_OFF; + mIsSendingTriggeredDuringTransferringState.set(false); + unbindService(); + notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_OFF); + } + + @Override + public void exit() { + if (DBG) logd("Exiting PowerOffState"); + logd("Attempting to bind to SatelliteGatewayService."); + bindService(); + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) log("PowerOffState: processing " + getWhatToString(msg.what)); + switch (msg.what) { + case EVENT_SATELLITE_ENABLED_STATE_CHANGED: + handleSatelliteEnabledStateChanged((boolean) msg.obj); + break; + } + // Ignore all unexpected events. + return HANDLED; + } + + private void handleSatelliteEnabledStateChanged(boolean on) { + if (on) { + transitionTo(mIdleState); + } + } + } + + private class IdleState extends State { + @Override + public void enter() { + if (DBG) logd("Entering IdleState"); + mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_IDLE; + mIsSendingTriggeredDuringTransferringState.set(false); + //Enable Cellular Modem scanning + mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, null); + notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE); + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) log("IdleState: processing " + getWhatToString(msg.what)); + switch (msg.what) { + case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED: + handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj); + break; + case EVENT_SATELLITE_ENABLED_STATE_CHANGED: + handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "IdleState"); + break; + } + // Ignore all unexpected events. + return HANDLED; + } + + private void handleEventDatagramTransferStateChanged( + @NonNull DatagramTransferState datagramTransferState) { + if ((datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING) + || (datagramTransferState.receiveState + == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING)) { + transitionTo(mTransferringState); + } + } + + @Override + public void exit() { + if (DBG) logd("Exiting IdleState"); + //Disable Cellular Modem Scanning + mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null); + } + } + + private class TransferringState extends State { + @Override + public void enter() { + if (DBG) logd("Entering TransferringState"); + mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING; + notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING); + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) log("TransferringState: processing " + getWhatToString(msg.what)); + switch (msg.what) { + case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED: + handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj); + return HANDLED; + case EVENT_SATELLITE_ENABLED_STATE_CHANGED: + handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "TransferringState"); + break; + } + // Ignore all unexpected events. + return HANDLED; + } + + private void handleEventDatagramTransferStateChanged( + @NonNull DatagramTransferState datagramTransferState) { + if (isSending(datagramTransferState.sendState) || isReceiving( + datagramTransferState.receiveState)) { + // Stay at transferring state. + } else if ((datagramTransferState.sendState + == SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED) + || (datagramTransferState.receiveState + == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED)) { + transitionTo(mIdleState); + } else { + transitionTo(mListeningState); + } + } + } + + private class ListeningState extends State { + @Override + public void enter() { + if (DBG) logd("Entering ListeningState"); + + mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_LISTENING; + long timeoutMillis = updateListeningMode(true); + sendMessageDelayed(EVENT_LISTENING_TIMER_TIMEOUT, timeoutMillis); + mIsSendingTriggeredDuringTransferringState.set(false); + notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_LISTENING); + } + + @Override + public void exit() { + removeMessages(EVENT_LISTENING_TIMER_TIMEOUT); + updateListeningMode(false); + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) log("ListeningState: processing " + getWhatToString(msg.what)); + switch (msg.what) { + case EVENT_LISTENING_TIMER_TIMEOUT: + transitionTo(mIdleState); + break; + case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED: + handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj); + break; + case EVENT_SATELLITE_ENABLED_STATE_CHANGED: + handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "ListeningState"); + break; + } + // Ignore all unexpected events. + return HANDLED; + } + + private long updateListeningMode(boolean enabled) { + long timeoutMillis; + if (mIsSendingTriggeredDuringTransferringState.get()) { + timeoutMillis = mSatelliteStayAtListeningFromSendingMillis; + } else { + timeoutMillis = mSatelliteStayAtListeningFromReceivingMillis; + } + mSatelliteModemInterface.requestSatelliteListeningEnabled( + enabled, (int) timeoutMillis, null); + return timeoutMillis; + } + + private void handleEventDatagramTransferStateChanged( + @NonNull DatagramTransferState datagramTransferState) { + if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING + || datagramTransferState.receiveState + == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING) { + transitionTo(mTransferringState); + } + } + } + + /** + * @return the string for msg.what + */ + @Override + protected String getWhatToString(int what) { + String whatString; + switch (what) { + case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED: + whatString = "EVENT_DATAGRAM_TRANSFER_STATE_CHANGED"; + break; + case EVENT_LISTENING_TIMER_TIMEOUT: + whatString = "EVENT_LISTENING_TIMER_TIMEOUT"; + break; + case EVENT_SATELLITE_ENABLED_STATE_CHANGED: + whatString = "EVENT_SATELLITE_ENABLED_STATE_CHANGED"; + break; + default: + whatString = "UNKNOWN EVENT " + what; + } + return whatString; + } + + private void setInitialState(boolean isSatelliteSupported) { + if (isSatelliteSupported) { + setInitialState(mPowerOffState); + } else { + setInitialState(mUnavailableState); + } + } + + private void notifyStateChangedEvent(@SatelliteManager.SatelliteModemState int state) { + List toBeRemoved = new ArrayList<>(); + mListeners.values().forEach(listener -> { + try { + listener.onSatelliteModemStateChanged(state); + } catch (RemoteException e) { + logd("notifyStateChangedEvent RemoteException: " + e); + toBeRemoved.add(listener); + } + }); + + toBeRemoved.forEach(listener -> { + mListeners.remove(listener.asBinder()); + }); + } + + private void handleSatelliteEnabledStateChanged(boolean off, String caller) { + if (off) { + transitionTo(mPowerOffState); + } else { + loge(caller + ": Unexpected satellite radio powered-on state changed event"); + } + } + + private boolean isSending(@SatelliteManager.SatelliteDatagramTransferState int sendState) { + return (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING + || sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS); + } + + private boolean isReceiving(@SatelliteManager.SatelliteDatagramTransferState int receiveState) { + return (receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING + || receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS + || receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE); + } + + @NonNull + private String getSatelliteGatewayPackageName() { + if (!TextUtils.isEmpty(mSatelliteGatewayServicePackageName)) { + return mSatelliteGatewayServicePackageName; + } + return TextUtils.emptyIfNull(mContext.getResources().getString( + R.string.config_satellite_gateway_service_package)); + } + + private void bindService() { + synchronized (mLock) { + if (mIsBinding || mIsBound) return; + mIsBinding = true; + } + mExponentialBackoff.start(); + + String packageName = getSatelliteGatewayPackageName(); + if (TextUtils.isEmpty(packageName)) { + loge("Unable to bind to the satellite gateway service because the package is" + + " undefined."); + // Since the package name comes from static device configs, stop retry because + // rebind will continue to fail without a valid package name. + synchronized (mLock) { + mIsBinding = false; + } + mExponentialBackoff.stop(); + return; + } + Intent intent = new Intent(SatelliteGatewayService.SERVICE_INTERFACE); + intent.setPackage(packageName); + + mSatelliteGatewayServiceConnection = new SatelliteGatewayServiceConnection(); + try { + boolean success = mContext.bindService( + intent, mSatelliteGatewayServiceConnection, Context.BIND_AUTO_CREATE); + if (success) { + logd("Successfully bound to the satellite gateway service."); + } else { + synchronized (mLock) { + mIsBinding = false; + } + mExponentialBackoff.notifyFailed(); + loge("Error binding to the satellite gateway service. Retrying in " + + mExponentialBackoff.getCurrentDelay() + " ms."); + } + } catch (Exception e) { + synchronized (mLock) { + mIsBinding = false; + } + mExponentialBackoff.notifyFailed(); + loge("Exception binding to the satellite gateway service. Retrying in " + + mExponentialBackoff.getCurrentDelay() + " ms. Exception: " + e); + } + } + + private void unbindService() { + logd("unbindService"); + mExponentialBackoff.stop(); + mSatelliteGatewayService = null; + synchronized (mLock) { + mIsBinding = false; + mIsBound = false; + } + if (mSatelliteGatewayServiceConnection != null) { + mContext.unbindService(mSatelliteGatewayServiceConnection); + mSatelliteGatewayServiceConnection = null; + } + } + private class SatelliteGatewayServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + logd("onServiceConnected: ComponentName=" + name); + synchronized (mLock) { + mIsBound = true; + mIsBinding = false; + } + mSatelliteGatewayService = ISatelliteGateway.Stub.asInterface(service); + mExponentialBackoff.stop(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + loge("onServiceDisconnected: Waiting for reconnect."); + synchronized (mLock) { + mIsBinding = false; + mIsBound = false; + } + mSatelliteGatewayService = null; + } + + @Override + public void onBindingDied(ComponentName name) { + loge("onBindingDied: Unbinding and rebinding service."); + synchronized (mLock) { + mIsBound = false; + mIsBinding = false; + } + unbindService(); + mExponentialBackoff.start(); + } + } + + private boolean isMockModemAllowed() { + return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)); + } + + private static long getSatelliteStayAtListeningFromSendingMillis() { + if (sInstance != null && sInstance.isDemoMode()) { + return DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS; + } else { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY, + SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS, + DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS); + } + } + + private static long getSatelliteStayAtListeningFromReceivingMillis() { + if (sInstance != null && sInstance.isDemoMode()) { + return DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS; + } else { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY, + SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS, + DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS); + } + } +} diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java new file mode 100644 index 0000000000000000000000000000000000000000..7a1de7cfdbcc566ace276a993d72146187e4fc02 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java @@ -0,0 +1,370 @@ +/* + * 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.metrics; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.telephony.satellite.SatelliteManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.metrics.SatelliteStats; + +/** + * Stats to log to satellite metrics + */ +public class ControllerMetricsStats { + private static final int ADD_COUNT = 1; + private static final String TAG = ControllerMetricsStats.class.getSimpleName(); + private static final boolean DBG = false; + + private static ControllerMetricsStats sInstance; + + private final Context mContext; + private SatelliteStats mSatelliteStats; + + private long mSatelliteOnTimeMillis; + private int mBatteryLevelWhenServiceOn; + private boolean mIsSatelliteModemOn; + private Boolean mIsBatteryCharged = null; + private int mBatteryChargedStartTimeSec; + private int mTotalBatteryChargeTimeSec; + + /** + * @return The singleton instance of ControllerMetricsStats. + */ + public static ControllerMetricsStats getInstance() { + if (sInstance == null) { + loge("ControllerMetricsStats was not yet initialized."); + } + return sInstance; + } + + /** + * Create the ControllerMetricsStats singleton instance. + * + * @param context The Context for the ControllerMetricsStats. + * @return The singleton instance of ControllerMetricsStats. + */ + public static ControllerMetricsStats make(@NonNull Context context) { + if (sInstance == null) { + sInstance = new ControllerMetricsStats(context); + } + return sInstance; + } + + /** + * Create the ControllerMetricsStats singleton instance, testing purpose only. + * + * @param context The Context for the ControllerMetricsStats. + * @param satelliteStats SatelliteStats instance to test + * @return The singleton instance of ControllerMetricsStats. + */ + @VisibleForTesting + public static ControllerMetricsStats make(@NonNull Context context, + @NonNull SatelliteStats satelliteStats) { + if (sInstance == null) { + sInstance = new ControllerMetricsStats(context, satelliteStats); + } + return sInstance; + } + + /** + * Create the ControllerMetricsStats to manage metrics report for + * {@link SatelliteStats.SatelliteControllerParams} + * @param context The Context for the ControllerMetricsStats. + */ + ControllerMetricsStats(@NonNull Context context) { + mContext = context; + mSatelliteStats = SatelliteStats.getInstance(); + } + + /** + * Create the ControllerMetricsStats to manage metrics report for + * {@link SatelliteStats.SatelliteControllerParams} + * + * @param context The Context for the ControllerMetricsStats. + * @param satelliteStats SatelliteStats object used for testing purpose + */ + @VisibleForTesting + protected ControllerMetricsStats(@NonNull Context context, + @NonNull SatelliteStats satelliteStats) { + mContext = context; + mSatelliteStats = satelliteStats; + } + + + /** Report a counter when an attempt for satellite service on is successfully done */ + public void reportServiceEnablementSuccessCount() { + logd("reportServiceEnablementSuccessCount()"); + mSatelliteStats.onSatelliteControllerMetrics( + new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfSatelliteServiceEnablementsSuccess(ADD_COUNT) + .build()); + } + + /** Report a counter when an attempt for satellite service on is failed */ + public void reportServiceEnablementFailCount() { + logd("reportServiceEnablementSuccessCount()"); + mSatelliteStats.onSatelliteControllerMetrics( + new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfSatelliteServiceEnablementsFail(ADD_COUNT) + .build()); + } + + /** Report a counter when an attempt for outgoing datagram is successfully done */ + public void reportOutgoingDatagramSuccessCount( + @NonNull @SatelliteManager.DatagramType int datagramType) { + SatelliteStats.SatelliteControllerParams controllerParam; + if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfOutgoingDatagramSuccess(ADD_COUNT) + .setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT) + .build(); + } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfOutgoingDatagramSuccess(ADD_COUNT) + .setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT) + .build(); + } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfOutgoingDatagramSuccess(ADD_COUNT) + .build(); + } + logd("reportServiceEnablementSuccessCount(): " + controllerParam); + mSatelliteStats.onSatelliteControllerMetrics(controllerParam); + } + + /** Report a counter when an attempt for outgoing datagram is failed */ + public void reportOutgoingDatagramFailCount( + @NonNull @SatelliteManager.DatagramType int datagramType) { + SatelliteStats.SatelliteControllerParams controllerParam; + if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfOutgoingDatagramFail(ADD_COUNT) + .setCountOfDatagramTypeSosSmsFail(ADD_COUNT) + .build(); + } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfOutgoingDatagramFail(ADD_COUNT) + .setCountOfDatagramTypeLocationSharingFail(ADD_COUNT) + .build(); + } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfOutgoingDatagramFail(ADD_COUNT) + .build(); + } + logd("reportOutgoingDatagramFailCount(): " + controllerParam); + mSatelliteStats.onSatelliteControllerMetrics(controllerParam); + } + + /** Report a counter when an attempt for incoming datagram is failed */ + public void reportIncomingDatagramCount( + @NonNull @SatelliteManager.SatelliteError int result) { + SatelliteStats.SatelliteControllerParams controllerParam; + if (result == SatelliteManager.SATELLITE_ERROR_NONE) { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfIncomingDatagramSuccess(ADD_COUNT) + .build(); + } else { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfIncomingDatagramFail(ADD_COUNT) + .build(); + } + logd("reportIncomingDatagramCount(): " + controllerParam); + mSatelliteStats.onSatelliteControllerMetrics(controllerParam); + } + + /** Report a counter when an attempt for de-provision is success or not */ + public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteError int result) { + SatelliteStats.SatelliteControllerParams controllerParam; + if (result == SatelliteManager.SATELLITE_ERROR_NONE) { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfProvisionSuccess(ADD_COUNT) + .build(); + } else { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfProvisionFail(ADD_COUNT) + .build(); + } + logd("reportProvisionCount(): " + controllerParam); + mSatelliteStats.onSatelliteControllerMetrics(controllerParam); + } + + /** Report a counter when an attempt for de-provision is success or not */ + public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteError int result) { + SatelliteStats.SatelliteControllerParams controllerParam; + if (result == SatelliteManager.SATELLITE_ERROR_NONE) { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfDeprovisionSuccess(ADD_COUNT) + .build(); + } else { + controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfDeprovisionFail(ADD_COUNT) + .build(); + } + logd("reportDeprovisionCount(): " + controllerParam); + mSatelliteStats.onSatelliteControllerMetrics(controllerParam); + } + + /** Return the total service up time for satellite service */ + @VisibleForTesting + public int captureTotalServiceUpTimeSec() { + long totalTimeMillis = getCurrentTime() - mSatelliteOnTimeMillis; + mSatelliteOnTimeMillis = 0; + return (int) (totalTimeMillis / 1000); + } + + /** Return the total battery charge time while satellite service is on */ + @VisibleForTesting + public int captureTotalBatteryChargeTimeSec() { + int totalTime = mTotalBatteryChargeTimeSec; + mTotalBatteryChargeTimeSec = 0; + return totalTime; + } + + /** Capture the satellite service on time and register battery monitor */ + public void onSatelliteEnabled() { + if (!isSatelliteModemOn()) { + mIsSatelliteModemOn = true; + + startCaptureBatteryLevel(); + + // log the timestamp of the satellite modem power on + mSatelliteOnTimeMillis = getCurrentTime(); + + // register broadcast receiver for monitoring battery status change + IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + + logd("register BatteryStatusReceiver"); + mContext.registerReceiver(mBatteryStatusReceiver, filter); + } + } + + /** Capture the satellite service off time and de-register battery monitor */ + public void onSatelliteDisabled() { + if (isSatelliteModemOn()) { + mIsSatelliteModemOn = false; + + logd("unregister BatteryStatusReceiver"); + mContext.unregisterReceiver(mBatteryStatusReceiver); + + int totalServiceUpTime = captureTotalServiceUpTimeSec(); + int batteryConsumptionPercent = captureTotalBatteryConsumptionPercent(mContext); + int totalBatteryChargeTime = captureTotalBatteryChargeTimeSec(); + + // report metrics about service up time and battery + SatelliteStats.SatelliteControllerParams controllerParam = + new SatelliteStats.SatelliteControllerParams.Builder() + .setTotalServiceUptimeSec(totalServiceUpTime) + .setTotalBatteryConsumptionPercent(batteryConsumptionPercent) + .setTotalBatteryChargedTimeSec(totalBatteryChargeTime) + .build(); + logd("onSatelliteDisabled(): " + controllerParam); + mSatelliteStats.onSatelliteControllerMetrics(controllerParam); + } + } + + /** Log the total battery charging time when satellite service is on */ + private void updateSatelliteBatteryChargeTime(boolean isCharged) { + logd("updateSatelliteBatteryChargeTime(" + isCharged + ")"); + // update only when the charge state has changed + if (mIsBatteryCharged == null || isCharged != mIsBatteryCharged) { + mIsBatteryCharged = isCharged; + + // When charged, log the start time of battery charging + if (isCharged) { + mBatteryChargedStartTimeSec = (int) (getCurrentTime() / 1000); + // When discharged, log the accumulated total battery charging time. + } else { + mTotalBatteryChargeTimeSec += + (int) (getCurrentTime() / 1000) + - mBatteryChargedStartTimeSec; + mBatteryChargedStartTimeSec = 0; + } + } + } + + /** Capture the battery level when satellite service is on */ + @VisibleForTesting + public void startCaptureBatteryLevel() { + try { + BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class); + mBatteryLevelWhenServiceOn = + batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + logd("sBatteryLevelWhenServiceOn = " + mBatteryLevelWhenServiceOn); + } catch (NullPointerException e) { + loge("BatteryManager is null"); + } + } + + /** Capture the total consumption level when service is off */ + @VisibleForTesting + public int captureTotalBatteryConsumptionPercent(Context context) { + try { + BatteryManager batteryManager = context.getSystemService(BatteryManager.class); + int currentLevel = + batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + return Math.max((mBatteryLevelWhenServiceOn - currentLevel), 0); + } catch (NullPointerException e) { + loge("BatteryManager is null"); + return 0; + } + } + + /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */ + private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() { + private long mLastUpdatedTime = 0; + private static final long UPDATE_INTERVAL = 60 * 1000; + + @Override + public void onReceive(Context context, Intent intent) { + long currentTime = getCurrentTime(); + if (currentTime - mLastUpdatedTime > UPDATE_INTERVAL) { + mLastUpdatedTime = currentTime; + int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + boolean isCharged = (status == BatteryManager.BATTERY_STATUS_CHARGING); + logd("Battery is charged(" + isCharged + ")"); + updateSatelliteBatteryChargeTime(isCharged); + } + } + }; + + @VisibleForTesting + public boolean isSatelliteModemOn() { + return mIsSatelliteModemOn; + } + + @VisibleForTesting + public long getCurrentTime() { + return System.currentTimeMillis(); + } + + private static void logd(@NonNull String log) { + if (DBG) { + Log.d(TAG, log); + } + } + + private static void loge(@NonNull String log) { + Log.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java new file mode 100644 index 0000000000000000000000000000000000000000..38696aabe2d169046f4014d7a5368a66c47c5d37 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java @@ -0,0 +1,110 @@ +/* + * 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.metrics; + +import android.annotation.NonNull; +import android.telephony.satellite.SatelliteManager; +import android.util.Log; + +import com.android.internal.telephony.metrics.SatelliteStats; + +/** + * Stats to log to satellite metrics + */ +public class ProvisionMetricsStats { + private static final String TAG = ProvisionMetricsStats.class.getSimpleName(); + private static final boolean DBG = false; + + private static ProvisionMetricsStats sInstance = null; + + public static final int INVALID_TIME = -1; + + private int mResultCode; + private int mProvisioningStartTimeSec; + private boolean mIsProvisionRequest; + private boolean mIsCanceled; + + private ProvisionMetricsStats() { + initializeProvisionParams(); + } + + /** + * Returns the Singleton instance of ProvisionMetricsStats class. + * If an instance of the Singleton class has not been created, + * it creates a new instance and returns it. Otherwise, it returns + * the existing instance. + * @return the Singleton instance of ProvisionMetricsStats + */ + public static ProvisionMetricsStats getOrCreateInstance() { + if (sInstance == null) { + logd("Create new ProvisionMetricsStats."); + sInstance = new ProvisionMetricsStats(); + } + return sInstance; + } + + /** Sets the resultCode for provision metrics */ + public ProvisionMetricsStats setResultCode(@SatelliteManager.SatelliteError int error) { + mResultCode = error; + return this; + } + + /** Sets the start time of provisioning */ + public void setProvisioningStartTime() { + mProvisioningStartTimeSec = (int) (System.currentTimeMillis() / 1000); + } + + /** Sets the isProvisionRequest to indicate whether provision or de-provision */ + public ProvisionMetricsStats setIsProvisionRequest(boolean isProvisionRequest) { + mIsProvisionRequest = isProvisionRequest; + return this; + } + + /** Sets the isCanceled to know whether the provision is canceled */ + public ProvisionMetricsStats setIsCanceled(boolean isCanceled) { + mIsCanceled = isCanceled; + return this; + } + + /** Report the provision metrics atoms to PersistAtomsStorage in telephony */ + public void reportProvisionMetrics() { + SatelliteStats.SatelliteProvisionParams provisionParams = + new SatelliteStats.SatelliteProvisionParams.Builder() + .setResultCode(mResultCode) + .setProvisioningTimeSec((int) + (System.currentTimeMillis() / 1000) - mProvisioningStartTimeSec) + .setIsProvisionRequest(mIsProvisionRequest) + .setIsCanceled(mIsCanceled) + .build(); + SatelliteStats.getInstance().onSatelliteProvisionMetrics(provisionParams); + logd(provisionParams.toString()); + initializeProvisionParams(); + } + + private void initializeProvisionParams() { + mResultCode = -1; + mProvisioningStartTimeSec = INVALID_TIME; + mIsProvisionRequest = false; + mIsCanceled = false; + } + + private static void logd(@NonNull String log) { + if (DBG) { + Log.d(TAG, log); + } + } +} diff --git a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java new file mode 100644 index 0000000000000000000000000000000000000000..776ba640b5296576841d10863cf561010a4d509a --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java @@ -0,0 +1,97 @@ +/* + * 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.metrics; + +import android.annotation.NonNull; +import android.telephony.satellite.SatelliteManager; +import android.util.Log; + +import com.android.internal.telephony.metrics.SatelliteStats; + +/** + * Stats to log to satellite session metrics + */ +public class SessionMetricsStats { + private static final String TAG = SessionMetricsStats.class.getSimpleName(); + private static final boolean DBG = false; + + private static SessionMetricsStats sInstance = null; + private @SatelliteManager.SatelliteError int mInitializationResult; + private @SatelliteManager.NTRadioTechnology int mRadioTechnology; + + private SessionMetricsStats() { + initializeSessionMetricsParam(); + } + + /** + * Returns the Singleton instance of SessionMetricsStats class. + * If an instance of the Singleton class has not been created, + * it creates a new instance and returns it. Otherwise, it returns + * the existing instance. + * @return the Singleton instance of SessionMetricsStats + */ + public static SessionMetricsStats getInstance() { + if (sInstance == null) { + loge("create new SessionMetricsStats."); + sInstance = new SessionMetricsStats(); + } + return sInstance; + } + + /** Sets the satellite initialization result */ + public SessionMetricsStats setInitializationResult( + @SatelliteManager.SatelliteError int result) { + logd("setInitializationResult(" + result + ")"); + mInitializationResult = result; + return this; + } + + /** Sets the satellite ratio technology */ + public SessionMetricsStats setRadioTechnology( + @SatelliteManager.NTRadioTechnology int radioTechnology) { + logd("setRadioTechnology(" + radioTechnology + ")"); + mRadioTechnology = radioTechnology; + return this; + } + + /** Report the session metrics atoms to PersistAtomsStorage in telephony */ + public void reportSessionMetrics() { + SatelliteStats.SatelliteSessionParams sessionParams = + new SatelliteStats.SatelliteSessionParams.Builder() + .setSatelliteServiceInitializationResult(mInitializationResult) + .setSatelliteTechnology(mRadioTechnology) + .build(); + logd(sessionParams.toString()); + SatelliteStats.getInstance().onSatelliteSessionMetrics(sessionParams); + initializeSessionMetricsParam(); + } + + private void initializeSessionMetricsParam() { + mInitializationResult = SatelliteManager.SATELLITE_ERROR_NONE; + mRadioTechnology = SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN; + } + + private static void logd(@NonNull String log) { + if (DBG) { + Log.d(TAG, log); + } + } + + private static void loge(@NonNull String log) { + Log.e(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java index 087aee642b759ee5b6c6474415f984ce3983e9bb..b90dc5edf5038d2d25d57e3b574543ff27448cca 100644 --- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java +++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java @@ -81,7 +81,7 @@ import java.util.stream.Collectors; * to the database should go through {@link SubscriptionManagerService}. */ public class SubscriptionDatabaseManager extends Handler { - private static final String LOG_TAG = "SDM"; + private static final String LOG_TAG = "SDMGR"; /** Whether enabling verbose debugging message or not. */ private static final boolean VDBG = false; @@ -91,7 +91,6 @@ public class SubscriptionDatabaseManager extends Handler { /** The mapping from {@link SimInfo} table to {@link SubscriptionInfoInternal} get methods. */ private static final Map> - // TODO: Support SimInfo.COLUMN_CB_XXX which are still used by wear. SUBSCRIPTION_GET_METHOD_MAP = Map.ofEntries( new AbstractMap.SimpleImmutableEntry<>( SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID, diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java index d7b572516b1668e8ef98d3dbce2e6a52c3dd5981..b917698998ff176395ac29efdef2ec7dcdc0a93d 100644 --- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java +++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java @@ -1200,30 +1200,10 @@ public class SubscriptionInfoInternal { .build(); } - /** - * Get ID stripped PII information on user build. - * - * @param id The PII id. - * - * @return The stripped string. - */ - public static String givePrintableId(String id) { - String idToPrint = null; - if (id != null) { - int len = id.length(); - if (len > 6 && !TelephonyUtils.IS_DEBUGGABLE) { - idToPrint = id.substring(0, len - 6) + Rlog.pii(false, id.substring(len - 6)); - } else { - idToPrint = id; - } - } - return idToPrint; - } - @Override public String toString() { return "[SubscriptionInfoInternal: id=" + mId - + " iccId=" + givePrintableId(mIccId) + + " iccId=" + SubscriptionInfo.getPrintableId(mIccId) + " simSlotIndex=" + mSimSlotIndex + " portIndex=" + mPortIndex + " isEmbedded=" + mIsEmbedded @@ -1243,7 +1223,7 @@ public class SubscriptionInfoInternal { + " mnc=" + mMnc + " ehplmns=" + mEhplmns + " hplmns=" + mHplmns - + " cardString=" + givePrintableId(mCardString) + + " cardString=" + SubscriptionInfo.getPrintableId(mCardString) + " cardId=" + mCardId + " nativeAccessRules=" + IccUtils.bytesToHexString(mNativeAccessRules) + " carrierConfigAccessRules=" + IccUtils.bytesToHexString( @@ -1261,7 +1241,7 @@ public class SubscriptionInfoInternal { + " wifiCallingModeForRoaming=" + ImsMmTelManager.wifiCallingModeToString(mWifiCallingModeForRoaming) + " enabledMobileDataPolicies=" + mEnabledMobileDataPolicies - + " imsi=" + givePrintableId(mImsi) + + " imsi=" + SubscriptionInfo.getPrintableId(mImsi) + " rcsUceEnabled=" + mIsRcsUceEnabled + " crossSimCallingEnabled=" + mIsCrossSimCallingEnabled + " rcsConfig=" + IccUtils.bytesToHexString(mRcsConfig) diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java index 2ac12a8cb5ec19910acd45dcd9d2ae4d05d3b698..a3377a277b929bccff3376256f5f0e8bd5e2cddb 100644 --- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java +++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java @@ -46,6 +46,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.TelephonyServiceManager; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.provider.Telephony.SimInfo; import android.service.carrier.CarrierIdentifier; @@ -90,7 +91,6 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.ProxyController; import com.android.internal.telephony.RILConstants; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyPermissions; import com.android.internal.telephony.data.PhoneSwitcher; @@ -548,7 +548,6 @@ public class SubscriptionManagerService extends ISub.Stub { }); SubscriptionManager.invalidateSubscriptionManagerServiceCaches(); - SubscriptionManager.invalidateSubscriptionManagerServiceEnabledCaches(); mContext.registerReceiver(new BroadcastReceiver() { @Override @@ -997,8 +996,7 @@ public class SubscriptionManagerService extends ISub.Stub { int subId = mSubscriptionDatabaseManager.insertSubscriptionInfo(builder.build()); logl("insertSubscriptionInfo: Inserted a new subscription. subId=" + subId - + ", slotIndex=" + slotIndex + ", iccId=" - + SubscriptionInfo.givePrintableIccid(iccId) + + ", slotIndex=" + slotIndex + ", iccId=" + SubscriptionInfo.getPrintableId(iccId) + ", displayName=" + displayName + ", type=" + SubscriptionManager.subscriptionTypeToString(subscriptionType)); return subId; @@ -1089,9 +1087,11 @@ public class SubscriptionManagerService extends ISub.Stub { builder.setRemovableEmbedded(isRemovable); // override DISPLAY_NAME if the priority of existing nameSource is <= carrier - if (getNameSourcePriority(nameSource) <= getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_CARRIER)) { - builder.setDisplayName(embeddedProfile.getNickname()); + String nickName = embeddedProfile.getNickname(); + if (nickName != null + && getNameSourcePriority(nameSource) <= getNameSourcePriority( + SubscriptionManager.NAME_SOURCE_CARRIER)) { + builder.setDisplayName(nickName); builder.setDisplayNameSource(SubscriptionManager.NAME_SOURCE_CARRIER); } builder.setProfileClass(embeddedProfile.getProfileClass()); @@ -1159,11 +1159,14 @@ public class SubscriptionManagerService extends ISub.Stub { } else { loge("The eSIM profiles update was not successful."); } + log("updateEmbeddedSubscriptions: Finished embedded subscription update."); + // The runnable will be executed in the main thread. Pre Android-U behavior. + mHandler.post(() -> { + if (callback != null) { + callback.run(); + } + }); }); - log("updateEmbeddedSubscriptions: Finished embedded subscription update."); - if (callback != null) { - callback.run(); - } } /** @@ -1274,7 +1277,7 @@ public class SubscriptionManagerService extends ISub.Stub { + TelephonyManager.simStateToString(simState)); for (UiccSlot slot : mUiccController.getUiccSlots()) { if (slot != null) { - log(" " + slot.toString()); + log(" " + slot); } } @@ -1309,16 +1312,13 @@ public class SubscriptionManagerService extends ISub.Stub { log("updateSubscription: SIM_STATE_NOT_READY is not a final state. Will update " + "subscription later."); return; - } - - if (!areUiccAppsEnabledOnCard(phoneId)) { + } else { logl("updateSubscription: UICC app disabled on slot " + phoneId); markSubscriptionsInactive(phoneId); } } else { - String iccId = getIccId(phoneId); - log("updateSubscription: Found iccId=" + SubscriptionInfo.givePrintableIccid(iccId) + log("updateSubscription: Found iccId=" + SubscriptionInfo.getPrintableId(iccId) + " on phone " + phoneId); // For eSIM switching, SIM absent will not happen. Below is to exam if we find ICCID @@ -1953,7 +1953,6 @@ public class SubscriptionManagerService extends ISub.Stub { * @see SubscriptionManager#requestEmbeddedSubscriptionInfoListRefresh */ @Override - // TODO: Remove this after SubscriptionController is removed. public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) { updateEmbeddedSubscriptions(List.of(cardId), null); } @@ -1975,7 +1974,7 @@ public class SubscriptionManagerService extends ISub.Stub { public int addSubInfo(@NonNull String iccId, @NonNull String displayName, int slotIndex, @SubscriptionType int subscriptionType) { enforcePermissions("addSubInfo", Manifest.permission.MODIFY_PHONE_STATE); - logl("addSubInfo: iccId=" + SubscriptionInfo.givePrintableIccid(iccId) + ", slotIndex=" + logl("addSubInfo: iccId=" + SubscriptionInfo.getPrintableId(iccId) + ", slotIndex=" + slotIndex + ", displayName=" + displayName + ", type=" + SubscriptionManager.subscriptionTypeToString(subscriptionType) + ", " + getCallingPackage()); @@ -2022,18 +2021,17 @@ public class SubscriptionManagerService extends ISub.Stub { * subscription type. * @param subscriptionType the type of subscription to be removed. * - * // TODO: Remove this terrible return value once SubscriptionController is removed. - * @return 0 if success, < 0 on error. + * @return {@code true} if succeeded, otherwise {@code false}. * * @throws NullPointerException if {@code uniqueId} is {@code null}. * @throws SecurityException if callers do not hold the required permission. */ @Override @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) - public int removeSubInfo(@NonNull String uniqueId, int subscriptionType) { + public boolean removeSubInfo(@NonNull String uniqueId, int subscriptionType) { enforcePermissions("removeSubInfo", Manifest.permission.MODIFY_PHONE_STATE); - logl("removeSubInfo: uniqueId=" + SubscriptionInfo.givePrintableIccid(uniqueId) + ", " + logl("removeSubInfo: uniqueId=" + SubscriptionInfo.getPrintableId(uniqueId) + ", " + SubscriptionManager.subscriptionTypeToString(subscriptionType) + ", " + getCallingPackage()); final long identity = Binder.clearCallingIdentity(); @@ -2042,15 +2040,15 @@ public class SubscriptionManagerService extends ISub.Stub { .getSubscriptionInfoInternalByIccId(uniqueId); if (subInfo == null) { loge("Cannot find subscription with uniqueId " + uniqueId); - return -1; + return false; } if (subInfo.getSubscriptionType() != subscriptionType) { loge("The subscription type does not match."); - return -1; + return false; } mSlotIndexToSubId.remove(subInfo.getSimSlotIndex()); mSubscriptionDatabaseManager.removeSubscriptionInfo(subInfo.getSubscriptionId()); - return 0; + return true; } finally { Binder.restoreCallingIdentity(identity); } @@ -2956,9 +2954,6 @@ public class SubscriptionManagerService extends ISub.Stub { * @param columnName Column name in the database. Note not all fields are supported. * @param value Value to store in the database. * - * // TODO: Remove return value after SubscriptionController is deleted. - * @return always 1 - * * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not * exposed. * @throws SecurityException if callers do not hold the required permission. @@ -2968,7 +2963,7 @@ public class SubscriptionManagerService extends ISub.Stub { */ @Override @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) - public int setSubscriptionProperty(int subId, @NonNull String columnName, + public void setSubscriptionProperty(int subId, @NonNull String columnName, @NonNull String value) { enforcePermissions("setSubscriptionProperty", Manifest.permission.MODIFY_PHONE_STATE); @@ -2989,7 +2984,6 @@ public class SubscriptionManagerService extends ISub.Stub { } mSubscriptionDatabaseManager.setSubscriptionProperty(subId, columnName, value); - return 1; } finally { Binder.restoreCallingIdentity(token); } @@ -3579,12 +3573,6 @@ public class SubscriptionManagerService extends ISub.Stub { public UserHandle getSubscriptionUserHandle(int subId) { enforcePermissions("getSubscriptionUserHandle", Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION); - - if (!mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enable_get_subscription_user_handle)) { - return null; - } - long token = Binder.clearCallingIdentity(); try { SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager @@ -3595,6 +3583,7 @@ public class SubscriptionManagerService extends ISub.Stub { } UserHandle userHandle = UserHandle.of(subInfo.getUserId()); + logv("getSubscriptionUserHandle subId = " + subId + " userHandle = " + userHandle); if (userHandle.getIdentifier() == UserHandle.USER_NULL) { return null; } @@ -3614,7 +3603,6 @@ public class SubscriptionManagerService extends ISub.Stub { * else {@code false} if subscription is not associated with user. * * @throws SecurityException if the caller doesn't have permissions required. - * @throws IllegalStateException if subscription service is not available. * */ @Override @@ -3632,6 +3620,13 @@ public class SubscriptionManagerService extends ISub.Stub { return true; } + List subIdList = subInfoList.stream().map(SubscriptionInfo::getSubscriptionId) + .collect(Collectors.toList()); + if (!subIdList.contains(subscriptionId)) { + // Return true as this subscription is not available on the device. + return true; + } + // Get list of subscriptions associated with this user. List associatedSubscriptionsList = getSubscriptionInfoListAssociatedWithUser(userHandle); @@ -3661,7 +3656,6 @@ public class SubscriptionManagerService extends ISub.Stub { * @return list of subscriptionInfo associated with the user. * * @throws SecurityException if the caller doesn't have permissions required. - * @throws IllegalStateException if subscription service is not available. * */ @Override @@ -3692,6 +3686,15 @@ public class SubscriptionManagerService extends ISub.Stub { } } + UserManager userManager = mContext.getSystemService(UserManager.class); + if ((userManager != null) + && (userManager.isManagedProfile(userHandle.getIdentifier()))) { + // For work profile, return subscriptions associated only with work profile + return subscriptionsAssociatedWithUser; + } + + // For all other profiles, if subscriptionsAssociatedWithUser is empty return all the + // subscriptionsWithNoAssociation. return subscriptionsAssociatedWithUser.isEmpty() ? subscriptionsWithNoAssociation : subscriptionsAssociatedWithUser; } finally { @@ -3699,16 +3702,6 @@ public class SubscriptionManagerService extends ISub.Stub { } } - /** - * @return {@code true} if using {@link SubscriptionManagerService} instead of - * {@link SubscriptionController}. - */ - //TODO: Removed before U AOSP public release. - @Override - public boolean isSubscriptionManagerServiceEnabled() { - return true; - } - /** * Called during setup wizard restore flow to attempt to restore the backed up sim-specific * configs to device for all existing SIMs in the subscription database {@link SimInfo}. @@ -3817,7 +3810,7 @@ public class SubscriptionManagerService extends ISub.Stub { public void updateSimStateForInactivePort(int slotIndex, @NonNull String iccId) { mHandler.post(() -> { logl("updateSimStateForInactivePort: slotIndex=" + slotIndex + ", iccId=" - + SubscriptionInfo.givePrintableIccid(iccId)); + + SubscriptionInfo.getPrintableId(iccId)); if (mSlotIndexToSubId.containsKey(slotIndex)) { // Re-enable the UICC application , so it will be in enabled state when it becomes // active again. (Pre-U behavior) @@ -3972,6 +3965,15 @@ public class SubscriptionManagerService extends ISub.Stub { mLocalLog.log(s); } + /** + * Log verbose messages. + * + * @param s verbose messages + */ + private void logv(@NonNull String s) { + Rlog.v(LOG_TAG, s); + } + /** * Dump the state of {@link SubscriptionManagerService}. * @@ -3981,79 +3983,87 @@ public class SubscriptionManagerService extends ISub.Stub { */ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @NonNull String[] args) { - IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); - pw.println(SubscriptionManagerService.class.getSimpleName() + ":"); - pw.println("Active modem count=" + mTelephonyManager.getActiveModemCount()); - pw.println("Logical SIM slot sub id mapping:"); - pw.increaseIndent(); - mSlotIndexToSubId.forEach((slotIndex, subId) - -> pw.println("Logical SIM slot " + slotIndex + ": subId=" + subId)); - pw.decreaseIndent(); - pw.println("ICCID:"); - pw.increaseIndent(); - for (int i = 0; i < mTelephonyManager.getActiveModemCount(); i++) { - pw.println("slot " + i + ": " + getIccId(i)); - } - pw.decreaseIndent(); - pw.println(); - pw.println("defaultSubId=" + getDefaultSubId()); - pw.println("defaultVoiceSubId=" + getDefaultVoiceSubId()); - pw.println("defaultDataSubId=" + getDefaultDataSubId()); - pw.println("activeDataSubId=" + getActiveDataSubscriptionId()); - pw.println("defaultSmsSubId=" + getDefaultSmsSubId()); - pw.println("areAllSubscriptionsLoaded=" + areAllSubscriptionsLoaded()); - pw.println(); - for (int i = 0; i < mSimState.length; i++) { - pw.println("mSimState[" + i + "]=" + TelephonyManager.simStateToString(mSimState[i])); - } - - pw.println(); - pw.println("Active subscriptions:"); - pw.increaseIndent(); - mSubscriptionDatabaseManager.getAllSubscriptions().stream() - .filter(SubscriptionInfoInternal::isActive).forEach(pw::println); - pw.decreaseIndent(); - - pw.println(); - pw.println("All subscriptions:"); - pw.increaseIndent(); - mSubscriptionDatabaseManager.getAllSubscriptions().forEach(pw::println); - pw.decreaseIndent(); - pw.println(); - - pw.print("Embedded subscriptions: ["); - pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream() - .filter(SubscriptionInfoInternal::isEmbedded) - .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) - .collect(Collectors.joining(", ")) + "]"); - - pw.print("Opportunistic subscriptions: ["); - pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream() - .filter(SubscriptionInfoInternal::isOpportunistic) - .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) - .collect(Collectors.joining(", ")) + "]"); - - pw.print("getAvailableSubscriptionInfoList: ["); - pw.println(getAvailableSubscriptionInfoList( - mContext.getOpPackageName(), mContext.getFeatureId()).stream() - .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) - .collect(Collectors.joining(", ")) + "]"); - - pw.print("getSelectableSubscriptionInfoList: ["); - pw.println(mSubscriptionManager.getSelectableSubscriptionInfoList().stream() - .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) - .collect(Collectors.joining(", ")) + "]"); - - if (mEuiccManager != null) { - pw.println("Euicc enabled=" + mEuiccManager.isEnabled()); - } - pw.println(); - pw.println("Local log:"); - pw.increaseIndent(); - mLocalLog.dump(fd, pw, args); - pw.decreaseIndent(); - pw.decreaseIndent(); - pw.println(); - mSubscriptionDatabaseManager.dump(fd, pw, args); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, + "Requires android.Manifest.permission.DUMP"); + final long token = Binder.clearCallingIdentity(); + try { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + pw.println(SubscriptionManagerService.class.getSimpleName() + ":"); + pw.println("Active modem count=" + mTelephonyManager.getActiveModemCount()); + pw.println("Logical SIM slot sub id mapping:"); + pw.increaseIndent(); + mSlotIndexToSubId.forEach((slotIndex, subId) + -> pw.println("Logical SIM slot " + slotIndex + ": subId=" + subId)); + pw.decreaseIndent(); + pw.println("ICCID:"); + pw.increaseIndent(); + for (int i = 0; i < mTelephonyManager.getActiveModemCount(); i++) { + pw.println("slot " + i + ": " + SubscriptionInfo.getPrintableId(getIccId(i))); + } + pw.decreaseIndent(); + pw.println(); + pw.println("defaultSubId=" + getDefaultSubId()); + pw.println("defaultVoiceSubId=" + getDefaultVoiceSubId()); + pw.println("defaultDataSubId=" + getDefaultDataSubId()); + pw.println("activeDataSubId=" + getActiveDataSubscriptionId()); + pw.println("defaultSmsSubId=" + getDefaultSmsSubId()); + pw.println("areAllSubscriptionsLoaded=" + areAllSubscriptionsLoaded()); + pw.println(); + for (int i = 0; i < mSimState.length; i++) { + pw.println("mSimState[" + i + "]=" + + TelephonyManager.simStateToString(mSimState[i])); + } + + pw.println(); + pw.println("Active subscriptions:"); + pw.increaseIndent(); + mSubscriptionDatabaseManager.getAllSubscriptions().stream() + .filter(SubscriptionInfoInternal::isActive).forEach(pw::println); + pw.decreaseIndent(); + + pw.println(); + pw.println("All subscriptions:"); + pw.increaseIndent(); + mSubscriptionDatabaseManager.getAllSubscriptions().forEach(pw::println); + pw.decreaseIndent(); + pw.println(); + + pw.print("Embedded subscriptions: ["); + pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream() + .filter(SubscriptionInfoInternal::isEmbedded) + .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) + .collect(Collectors.joining(", ")) + "]"); + + pw.print("Opportunistic subscriptions: ["); + pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream() + .filter(SubscriptionInfoInternal::isOpportunistic) + .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) + .collect(Collectors.joining(", ")) + "]"); + + pw.print("getAvailableSubscriptionInfoList: ["); + pw.println(getAvailableSubscriptionInfoList( + mContext.getOpPackageName(), mContext.getFeatureId()).stream() + .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) + .collect(Collectors.joining(", ")) + "]"); + + pw.print("getSelectableSubscriptionInfoList: ["); + pw.println(mSubscriptionManager.getSelectableSubscriptionInfoList().stream() + .map(subInfo -> String.valueOf(subInfo.getSubscriptionId())) + .collect(Collectors.joining(", ")) + "]"); + + if (mEuiccManager != null) { + pw.println("Euicc enabled=" + mEuiccManager.isEnabled()); + } + pw.println(); + pw.println("Local log:"); + pw.increaseIndent(); + mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); + pw.decreaseIndent(); + pw.println(); + mSubscriptionDatabaseManager.dump(fd, pw, args); + } finally { + Binder.restoreCallingIdentity(token); + } } } diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java b/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java index 21a6e37981ce355d57c80fbfee36ec19800e6cfa..90c94914f809352af963b5eb72ce605ec2260de6 100644 --- a/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java +++ b/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java @@ -23,10 +23,12 @@ import android.os.Handler; import android.os.Message; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.gsm.UsimPhoneBookManager; import java.util.ArrayList; import java.util.Iterator; +import java.util.Locale; /** * {@hide} @@ -58,14 +60,16 @@ public class AdnRecordCache extends Handler implements IccConstants { static final int EVENT_UPDATE_ADN_DONE = 2; //***** Constructor - - - AdnRecordCache(IccFileHandler fh) { mFh = fh; mUsimPhoneBookManager = new UsimPhoneBookManager(mFh, this); } + public AdnRecordCache(IccFileHandler fh, UsimPhoneBookManager usimPhoneBookManager) { + mFh = fh; + mUsimPhoneBookManager = usimPhoneBookManager; + } + //***** Called from SIMRecords /** @@ -156,14 +160,14 @@ public class AdnRecordCache extends Handler implements IccConstants { int extensionEF = extensionEfForEf(efid); if (extensionEF < 0) { sendErrorResponse(response, "EF is not known ADN-like EF:0x" + - Integer.toHexString(efid).toUpperCase()); + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } Message pendingResponse = mUserWriteResponse.get(efid); if (pendingResponse != null) { sendErrorResponse(response, "Have pending update for EF:0x" + - Integer.toHexString(efid).toUpperCase()); + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } @@ -190,16 +194,14 @@ public class AdnRecordCache extends Handler implements IccConstants { */ public void updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn, String pin2, Message response) { - int extensionEF; extensionEF = extensionEfForEf(efid); if (extensionEF < 0) { sendErrorResponse(response, "EF is not known ADN-like EF:0x" + - Integer.toHexString(efid).toUpperCase()); + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } - ArrayList oldAdnList; if (efid == EF_PBR) { @@ -207,13 +209,11 @@ public class AdnRecordCache extends Handler implements IccConstants { } else { oldAdnList = getRecordsIfLoaded(efid); } - if (oldAdnList == null) { sendErrorResponse(response, "Adn list not exist for EF:0x" + - Integer.toHexString(efid).toUpperCase()); + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } - int index = -1; int count = 1; for (Iterator it = oldAdnList.iterator(); it.hasNext(); ) { @@ -223,7 +223,6 @@ public class AdnRecordCache extends Handler implements IccConstants { } count++; } - if (index == -1) { sendErrorResponse(response, "Adn record don't exist for " + oldAdn); return; @@ -244,7 +243,7 @@ public class AdnRecordCache extends Handler implements IccConstants { if (pendingResponse != null) { sendErrorResponse(response, "Have pending update for EF:0x" + - Integer.toHexString(efid).toUpperCase()); + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } @@ -307,7 +306,7 @@ public class AdnRecordCache extends Handler implements IccConstants { if (response != null) { AsyncResult.forMessage(response).exception = new RuntimeException("EF is not known ADN-like EF:0x" + - Integer.toHexString(efid).toUpperCase()); + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); response.sendToTarget(); } @@ -342,7 +341,6 @@ public class AdnRecordCache extends Handler implements IccConstants { handleMessage(Message msg) { AsyncResult ar; int efid; - switch(msg.what) { case EVENT_LOAD_ALL_ADN_LIKE_DONE: /* arg1 is efid, obj.result is ArrayList*/ @@ -381,4 +379,24 @@ public class AdnRecordCache extends Handler implements IccConstants { break; } } + + @VisibleForTesting + protected void setAdnLikeWriters(int key, ArrayList waiters) { + mAdnLikeWaiters.put(EF_MBDN, waiters); + } + + @VisibleForTesting + protected void setAdnLikeFiles(int key, ArrayList adnRecordList) { + mAdnLikeFiles.put(EF_MBDN, adnRecordList); + } + + @VisibleForTesting + protected void setUserWriteResponse(int key, Message message) { + mUserWriteResponse.put(EF_MBDN, message); + } + + @VisibleForTesting + protected UsimPhoneBookManager getUsimPhoneBookManager() { + return mUsimPhoneBookManager; + } } diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java b/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java index a688c6e5d724b38d795daf8d77ec9549fa0cf8ac..5b0a6a3612cade65a2e11fa784274e97173d1062 100644 --- a/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java +++ b/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java @@ -59,6 +59,7 @@ public class AdnRecordLoader extends Handler { static final int EVENT_EF_LINEAR_RECORD_SIZE_DONE = 4; static final int EVENT_UPDATE_RECORD_DONE = 5; + static final int VOICEMAIL_ALPHATAG_ARG = 1; //***** Constructor @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -177,14 +178,34 @@ public class AdnRecordLoader extends Handler { data = adn.buildAdnString(recordSize[0]); if(data == null) { - throw new RuntimeException("wrong ADN format", - ar.exception); + /** + * The voicemail number saving to the SIM is in name(alphaTag) and number + * format. {@link recordSize[0]} indicates the SIM EF memory size that the + * sim can have to save both voicemail name and number. 14 byte of memory + * is reserved to save the voicemail number and remaining memory is reserved + * for the alphaTag. In case if we receive the alphaTag which is more than + * the reserved memory size then SIM will throw the exception and it don't + * save both the voicemail number and alphaTag. To avoid this problem, in + * case alphaTag length is more we nullify the alphaTag and save the same. + */ + if (mUserResponse.arg1 == VOICEMAIL_ALPHATAG_ARG) { + adn.mAlphaTag = null; + data = adn.buildAdnString(recordSize[0]); + } + if (data == null) { + throw new RuntimeException("wrong ADN format", + ar.exception); + } } - - mFh.updateEFLinearFixed(mEf, getEFPath(mEf), mRecordNumber, - data, mPin2, obtainMessage(EVENT_UPDATE_RECORD_DONE)); - + // Send adn record to caller to track the changes made to alphaTag + if (mUserResponse.arg1 == VOICEMAIL_ALPHATAG_ARG) { + mFh.updateEFLinearFixed(mEf, getEFPath(mEf), mRecordNumber, + data, mPin2, obtainMessage(EVENT_UPDATE_RECORD_DONE, adn)); + } else { + mFh.updateEFLinearFixed(mEf, getEFPath(mEf), mRecordNumber, + data, mPin2, obtainMessage(EVENT_UPDATE_RECORD_DONE)); + } mPendingExtLoads = 1; break; @@ -195,7 +216,12 @@ public class AdnRecordLoader extends Handler { ar.exception); } mPendingExtLoads = 0; - mResult = null; + // send the adn record back to caller through the result of AsyncResult + if (mUserResponse.arg1 == VOICEMAIL_ALPHATAG_ARG) { + mResult = ar.userObj; + } else { + mResult = null; + } break; case EVENT_ADN_LOAD_DONE: ar = (AsyncResult)(msg.obj); diff --git a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java index ec07780c8fcf3b202b17455b728988476e877224..f0d949dca1cb6446c3918f9d24b92f637f40ab75 100644 --- a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java +++ b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java @@ -20,6 +20,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.telephony.SubscriptionInfo; +import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; @@ -90,6 +91,30 @@ public class IccCardStatus { public IccSlotPortMapping mSlotPortMapping; + public MultipleEnabledProfilesMode mSupportedMepMode = MultipleEnabledProfilesMode.NONE; + + /** + * Set the MultipleEnabledProfilesMode according to the input mode. + */ + public void setMultipleEnabledProfilesMode(int mode) { + switch(mode) { + case 0: + mSupportedMepMode = MultipleEnabledProfilesMode.NONE; + break; + case 1: + mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A1; + break; + case 2: + mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A2; + break; + case 3: + mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; + break; + default: + throw new RuntimeException("Unrecognized RIL_MultipleEnabledProfilesMode: " + mode); + } + } + public void setCardState(int state) { switch(state) { case 0: @@ -172,8 +197,9 @@ public class IccCardStatus { } sb.append(",atr=").append(atr); - sb.append(",iccid=").append(SubscriptionInfo.givePrintableIccid(iccid)); + sb.append(",iccid=").append(SubscriptionInfo.getPrintableId(iccid)); sb.append(",eid=").append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, eid)); + sb.append(",SupportedMepMode=").append(mSupportedMepMode); sb.append(",SlotPortMapping=").append(mSlotPortMapping); sb.append("}"); diff --git a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java index 6c4ac699b17c4a5b15a075493a0c546acbc34026..5f8ccb482a71bcee4eed1ec2a046637e8ba964aa 100644 --- a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java +++ b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java @@ -22,6 +22,7 @@ import android.os.Build; import android.os.Handler; import android.os.Message; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; import java.util.ArrayList; @@ -108,7 +109,7 @@ public abstract class IccFileHandler extends Handler implements IccConstants { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected final String mAid; - static class LoadLinearFixedContext { + public static class LoadLinearFixedContext { int mEfid; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -145,14 +146,11 @@ public abstract class IccFileHandler extends Handler implements IccConstants { mOnLoaded = onLoaded; mPath = path; } + } - LoadLinearFixedContext(int efid, Message onLoaded) { - mEfid = efid; - mRecordNum = 1; - mLoadAll = true; - mOnLoaded = onLoaded; - mPath = null; - } + @VisibleForTesting + public int getEfid(LoadLinearFixedContext lc) { + return lc.mEfid; } /** @@ -164,6 +162,13 @@ public abstract class IccFileHandler extends Handler implements IccConstants { mCi = ci; } + @VisibleForTesting + public IccFileHandler(CommandsInterface ci) { + mParentApp = null; + mAid = null; + mCi = ci; + } + public void dispose() { } @@ -267,8 +272,7 @@ public abstract class IccFileHandler extends Handler implements IccConstants { * @param path Path of the EF on the card * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the size of data int */ - public void getEFTransparentRecordSize(int fileid, String path, Message onLoaded) { - String efPath = (path == null) ? getEFPath(fileid) : path; + public void getEFTransparentRecordSize(int fileid, Message onLoaded) { Message response = obtainMessage(EVENT_GET_EF_TRANSPARENT_SIZE_DONE, fileid, 0, onLoaded); mCi.iccIOForApp( COMMAND_GET_RESPONSE, @@ -283,16 +287,6 @@ public abstract class IccFileHandler extends Handler implements IccConstants { response); } - /** - * Get record size for a transparent EF - * - * @param fileid EF id - * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the size of the data int - */ - public void getEFTransparentRecordSize(int fileid, Message onLoaded) { - getEFTransparentRecordSize(fileid, getEFPath(fileid), onLoaded); - } - /** * Load all records from a SIM Linear Fixed EF * diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java index da112b166e39e8fcdc5c6a2acf96674cac221807..ae93b092834f25a9b7b9a7132b3943faebe87306 100644 --- a/src/java/com/android/internal/telephony/uicc/IccRecords.java +++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java @@ -257,6 +257,8 @@ public abstract class IccRecords extends Handler implements IccConstants { // call back received on this upon EF_SMSS record update. public static final int EVENT_SET_SMSS_RECORD_DONE = 201; + private static final int EVENT_GET_FDN_DONE = 202; + /** * There are two purposes for this class. First, each instance of AuthAsyncResponse acts as a * lock to for calling thead to wait in getIccSimChallengeResponse(). Second, pass the IMS @@ -269,7 +271,7 @@ public abstract class IccRecords extends Handler implements IccConstants { @Override public String toString() { - String iccIdToPrint = SubscriptionInfo.givePrintableIccid(mFullIccId); + String iccIdToPrint = SubscriptionInfo.getPrintableId(mFullIccId); return "mDestroyed=" + mDestroyed + " mContext=" + mContext + " mCi=" + mCi @@ -999,6 +1001,15 @@ public abstract class IccRecords extends Handler implements IccConstants { } break; + case EVENT_GET_FDN_DONE: + ar = (AsyncResult) msg.obj; + if (ar.exception != null) { + loge("Failed to read USIM EF_FDN field error=" + ar.exception); + } else { + log("EF_FDN read successfully"); + } + break; + default: super.handleMessage(msg); } @@ -1428,7 +1439,7 @@ public abstract class IccRecords extends Handler implements IccConstants { pw.println(" mRecordsToLoad=" + mRecordsToLoad); pw.println(" mRdnCache=" + mAdnCache); - String iccIdToPrint = SubscriptionInfo.givePrintableIccid(mFullIccId); + String iccIdToPrint = SubscriptionInfo.getPrintableId(mFullIccId); pw.println(" iccid=" + iccIdToPrint); pw.println(" mMsisdn=" + Rlog.pii(VDBG, mMsisdn)); pw.println(" mMsisdnTag=" + mMsisdnTag); @@ -1674,4 +1685,12 @@ public abstract class IccRecords extends Handler implements IccConstants { return mMsg; } } + + public void loadFdnRecords() { + if (mParentApp != null && mAdnCache != null) { + log("Loading FdnRecords"); + mAdnCache.requestLoadAllAdnLike(IccConstants.EF_FDN, EF_EXT2, + obtainMessage(EVENT_GET_FDN_DONE)); + } + } } diff --git a/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java b/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java index 9a5e10d984ff4d6e2e3896e1ef098207443df60a..4164a1e1ce7939161f5895364a8fde198a334b0e 100644 --- a/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java +++ b/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java @@ -51,7 +51,7 @@ public class IccSimPortInfo { public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{").append("iccid=") - .append(SubscriptionInfo.givePrintableIccid(mIccId)).append(",") + .append(SubscriptionInfo.getPrintableId(mIccId)).append(",") .append("logicalSlotIndex=").append(mLogicalSlotIndex).append(",") .append("portActive=").append(mPortActive) .append("}"); diff --git a/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java b/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java index 96a3a33ffd53b7be2f046c35518617d5f1fa53c7..8e55f7c55d64f98ce69cef30580889988385e439 100644 --- a/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java +++ b/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java @@ -30,11 +30,46 @@ public class IccSlotStatus { /* Added state active to check slotState in old HAL case.*/ public static final int STATE_ACTIVE = 1; + public enum MultipleEnabledProfilesMode { + /** + * If there is no jointly supported MEP mode, set supported MEP mode to NONE. + */ + NONE, + /** + * In case of MEP-A1, the ISD-R is selected on eSIM port 0 only and profiles are selected + * on eSIM ports 1 and higher, with the eSIM port being assigned by the LPA or platform. + */ + MEP_A1, + /** + * In case of MEP-A2, the ISD-R is selected on eSIM port 0 only and profiles are selected + * on eSIM ports 1 and higher, with the eSIM port being assigned by the eUICC. + */ + MEP_A2, + /** + * In case of MEP-B, profiles are selected on eSIM ports 0 and higher, with the ISD-R being + * selectable on any of these eSIM ports. + */ + MEP_B; + + public boolean isMepAMode() { + return (this == MEP_A1 || this == MEP_A2); + } + + public boolean isMepA1Mode() { + return this == MEP_A1; + } + + public boolean isMepMode() { + return this != NONE; + } + } + public IccCardStatus.CardState cardState; public String atr; public String eid; public IccSimPortInfo[] mSimPortInfos; + public MultipleEnabledProfilesMode mSupportedMepMode = MultipleEnabledProfilesMode.NONE; /** * Set the cardState according to the input state. @@ -58,6 +93,28 @@ public class IccSlotStatus { } } + /** + * Set the MultipleEnabledProfilesMode according to the input mode. + */ + public void setMultipleEnabledProfilesMode(int mode) { + switch(mode) { + case 0: + mSupportedMepMode = MultipleEnabledProfilesMode.NONE; + break; + case 1: + mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A1; + break; + case 2: + mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A2; + break; + case 3: + mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; + break; + default: + throw new RuntimeException("Unrecognized RIL_MultipleEnabledProfilesMode: " + mode); + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -72,6 +129,7 @@ public class IccSlotStatus { } else { sb.append("num_ports=null"); } + sb.append(", SupportedMepMode=" + mSupportedMepMode); sb.append("}"); return sb.toString(); } diff --git a/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java b/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java index 412295dde84227afb73f63e224a12ccf296a7ff9..7666f4cb05589aa4171ae082d3d703a8a0bbb0a0 100644 --- a/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java +++ b/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java @@ -38,6 +38,7 @@ import com.android.internal.telephony.util.NotificationChannelController; import java.util.Arrays; import java.util.List; +import java.util.Locale; /** * Utility methods for installing the carrier app when a SIM is insterted without the carrier app @@ -178,7 +179,7 @@ public class InstallCarrierAppUtils { */ @VisibleForTesting public static String getAppNameFromPackageName(String packageName, String mapString) { - packageName = packageName.toLowerCase(); + packageName = packageName.toLowerCase(Locale.ROOT); final String pairDelim = "\\s*;\\s*"; final String keyValueDelim = "\\s*:\\s*"; diff --git a/src/java/com/android/internal/telephony/uicc/IsimServiceTable.java b/src/java/com/android/internal/telephony/uicc/IsimServiceTable.java new file mode 100644 index 0000000000000000000000000000000000000000..289229cc90a76cfb9f25b73881127bb80e33606b --- /dev/null +++ b/src/java/com/android/internal/telephony/uicc/IsimServiceTable.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.uicc; + +public final class IsimServiceTable extends IccServiceTable { + private static final String TAG = "IsimServiceTable"; + public enum IsimService { + PCSCF_ADDRESS, + GBA, // Generic Bootstrapping Architecture (GBA) + HTTP_DIGEST, + GBA_LOCALKEY_ESTABLISHMENT, // GBA-based Local Key Establishment Mechanism + PCSCF_DISCOVERY_FOR_IMS, // Support of P-CSCF discovery for IMS Local Break Out + SMS, + SMSR, // Short Message Status Reports + SM_OVERIP_AND_DATA_DL_VIA_SMS_PP, // Support for SM-over-IP including data download via + // SMS-PP + COMMUNICATION_CONTROL_FOR_IMS_BY_ISIM, + UICC_ACCESS_TO_IMS + } + + public IsimServiceTable(byte[] table) { + super(table); + } + + public boolean isAvailable(IsimService service) { + return super.isAvailable(service.ordinal()); + } + + @Override + protected String getTag() { + return TAG; + } + + @Override + protected Object[] getValues() { + return IsimService.values(); + } + + public byte[] getISIMServiceTable() { + return mServiceTable; + } +} diff --git a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java index f9b51cf819ab60d2f7a3833744622d9b5f98f431..9591a4980041393c901312605460f152be74d41d 100644 --- a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java +++ b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java @@ -225,6 +225,11 @@ public class IsimUiccRecords extends IccRecords implements IsimRecords { } } + @VisibleForTesting + public EfIsimIstLoaded getIsimIstObject() { + return new EfIsimIstLoaded(); + } + private class EfIsimSmssLoaded implements IccRecords.IccRecordLoaded { @Override @@ -484,11 +489,11 @@ public class IsimUiccRecords extends IccRecords implements IsimRecords { pw.println("IsimRecords: " + this); pw.println(" extends:"); super.dump(fd, pw, args); + pw.println(" mIsimServiceTable=" + getIsimServiceTable()); if (DUMP_RECORDS) { pw.println(" mIsimImpi=" + mIsimImpi); pw.println(" mIsimDomain=" + mIsimDomain); pw.println(" mIsimImpu[]=" + Arrays.toString(mIsimImpu)); - pw.println(" mIsimIst" + mIsimIst); pw.println(" mIsimPcscf" + Arrays.toString(mIsimPcscf)); pw.println(" mPsismsc=" + mPsiSmsc); pw.println(" mSmss TPMR=" + getSmssTpmrValue()); @@ -496,6 +501,14 @@ public class IsimUiccRecords extends IccRecords implements IsimRecords { pw.flush(); } + // Just to return the Enums of service table to print in DUMP + private IsimServiceTable getIsimServiceTable() { + if (mIsimIst != null) { + byte[] istTable = IccUtils.hexStringToBytes(mIsimIst); + return new IsimServiceTable(istTable); + } + return null; + } @Override public int getVoiceMessageCount() { return 0; // Not applicable to Isim diff --git a/src/java/com/android/internal/telephony/uicc/PinStorage.java b/src/java/com/android/internal/telephony/uicc/PinStorage.java index 8623f0ac2ad6c19bc94cd217a35a0df03e9f1747..23769ad32d548282dddd91bcf7aa1c65fded5098 100644 --- a/src/java/com/android/internal/telephony/uicc/PinStorage.java +++ b/src/java/com/android/internal/telephony/uicc/PinStorage.java @@ -28,6 +28,7 @@ import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_COUNT_NOT_MATCHING_AFTER_REBOOT; import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_DECRYPTION_ERROR; import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_ERROR; +import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_KEY_MISSING; import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_REQUIRED_AFTER_REBOOT; import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_STORED_FOR_VERIFICATION; import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_FAILURE; @@ -722,7 +723,11 @@ public class PinStorage extends Handler { */ @Nullable private StoredPin decryptStoredPin(byte[] blob, @Nullable SecretKey secretKey) { - if (secretKey != null) { + if (secretKey == null) { + TelephonyStatsLog.write(PIN_STORAGE_EVENT, + PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_KEY_MISSING, + /* number_of_pins= */ 1, /* package_name= */ ""); + } else { try { byte[] decryptedPin = decrypt(secretKey, blob); if (decryptedPin.length > 0) { diff --git a/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java b/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java old mode 100755 new mode 100644 diff --git a/src/java/com/android/internal/telephony/uicc/PortUtils.java b/src/java/com/android/internal/telephony/uicc/PortUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..4a18b5688df9fd2a1b095599fa4413c3ee3ee5d4 --- /dev/null +++ b/src/java/com/android/internal/telephony/uicc/PortUtils.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.uicc; + +import android.annotation.NonNull; + +import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; + +/** + * Various methods, useful for dealing with port. + */ +public class PortUtils { + + /** + * Converts the port index to compatible with the HAL. + * + * @param mepMode supported MultipleEnabledProfilesMode + * @param portIndex port index + * @return target index according to the MultipleEnabledProfilesMode + */ + public static int convertToHalPortIndex(@NonNull MultipleEnabledProfilesMode mepMode, + int portIndex) { + // In case of MEP-A1 and MEP-A2, profiles are selected on eSIM Ports 1 and higher, hence + // HAL expects the ports are indexed with 1, 2... etc. + // So inorder to compatible with HAL, shift the port index. + return mepMode.isMepAMode() ? ++portIndex : portIndex; + } + + /** + * Converts the port index to compatible with the HAL. + * + * @param slotIndex physical slot index corresponding to the portIndex + * @param portIndex port index + * @return target port index according to the MultipleEnabledProfilesMode + */ + public static int convertToHalPortIndex(int slotIndex, int portIndex) { + return convertToHalPortIndex(UiccController.getInstance().getSupportedMepMode(slotIndex), + portIndex); + } + + /** + * Converts the port index to compatible with the platform. + * + * @param slotIndex physical slot index corresponding to the portIndex + * @param portIndex target port index + * @param cardState cardState + * @param supportedMepMode supported MEP mode + * @return shifted port index according to the MultipleEnabledProfilesMode + */ + public static int convertFromHalPortIndex(int slotIndex, int portIndex, + IccCardStatus.CardState cardState, MultipleEnabledProfilesMode supportedMepMode) { + // In case of MEP-A1 and MEP-A2, profiles are selected on eSIM Ports 1 and higher. + // But inorder to platform code MEP mode agnostic, platform always expects the ports + // are indexed with 0, 1... etc. Hence shift the target port index to be compatible + // with platform. + + // When the SIM_STATUS is related to CARDSTATE_ABSENT, CardStatus will not contain proper + // MEP mode info, fallback onto to the supportedMepMode data available in UiccSlot. + MultipleEnabledProfilesMode mepMode = cardState.isCardPresent() ? supportedMepMode + : UiccController.getInstance().getSupportedMepMode(slotIndex); + return mepMode.isMepAMode() ? --portIndex : portIndex; + } +} diff --git a/src/java/com/android/internal/telephony/uicc/RuimRecords.java b/src/java/com/android/internal/telephony/uicc/RuimRecords.java old mode 100755 new mode 100644 index 041b5dde624a5bc3d6b5f635ea60c4dfe0f054bc..2e490e3f84166f33d6d232ce3270fadbed4aac33 --- a/src/java/com/android/internal/telephony/uicc/RuimRecords.java +++ b/src/java/com/android/internal/telephony/uicc/RuimRecords.java @@ -690,7 +690,7 @@ public class RuimRecords extends IccRecords { mIccId = IccUtils.bcdToString(data, 0, data.length); mFullIccId = IccUtils.bchToString(data, 0, data.length); - log("iccid: " + SubscriptionInfo.givePrintableIccid(mFullIccId)); + log("iccid: " + SubscriptionInfo.getPrintableId(mFullIccId)); break; @@ -817,7 +817,6 @@ public class RuimRecords extends IccRecords { mLoaded.set(true); mRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null)); - // TODO: The below is hacky since the SubscriptionController may not be ready at this time. if (!TextUtils.isEmpty(mMdn)) { int phoneId = mParentApp.getUiccProfile().getPhoneId(); int subId = SubscriptionManager.getSubscriptionId(phoneId); diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java index 485838b1a876055f72a790142733bd2f1797fc23..a97b00bdae761a7fe0a6e613c865b955c4660165 100644 --- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java +++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java @@ -35,6 +35,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.MccTable; import com.android.internal.telephony.SmsConstants; @@ -167,7 +168,7 @@ public class SIMRecords extends IccRecords { private static final int EVENT_UPDATE_DONE = 14 + SIM_RECORD_EVENT_BASE; protected static final int EVENT_GET_PNN_DONE = 15 + SIM_RECORD_EVENT_BASE; protected static final int EVENT_GET_OPL_DONE = 16 + SIM_RECORD_EVENT_BASE; - private static final int EVENT_GET_SST_DONE = 17 + SIM_RECORD_EVENT_BASE; + protected static final int EVENT_GET_SST_DONE = 17 + SIM_RECORD_EVENT_BASE; private static final int EVENT_GET_ALL_SMS_DONE = 18 + SIM_RECORD_EVENT_BASE; private static final int EVENT_MARK_SMS_READ_DONE = 19 + SIM_RECORD_EVENT_BASE; private static final int EVENT_SET_MBDN_DONE = 20 + SIM_RECORD_EVENT_BASE; @@ -190,7 +191,6 @@ public class SIMRecords extends IccRecords { private static final int EVENT_SET_FPLMN_DONE = 43 + SIM_RECORD_EVENT_BASE; protected static final int EVENT_GET_SMSS_RECORD_DONE = 46 + SIM_RECORD_EVENT_BASE; protected static final int EVENT_GET_PSISMSC_DONE = 47 + SIM_RECORD_EVENT_BASE; - protected static final int EVENT_GET_FDN_DONE = 48 + SIM_RECORD_EVENT_BASE; // ***** Constructor @@ -280,6 +280,18 @@ public class SIMRecords extends IccRecords { return mUsimServiceTable; } + /** + * Fetches the USIM service table from UsimServiceTable + * + * @return HexString representation of USIM service table + */ + public String getSimServiceTable() { + if (mUsimServiceTable != null) { + return IccUtils.bytesToHexString(mUsimServiceTable.getUSIMServiceTable()); + } + return null; + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private int getExtFromEf(int ef) { int ext; @@ -387,18 +399,20 @@ public class SIMRecords extends IccRecords { mNewVoiceMailTag = alphaTag; AdnRecord adn = new AdnRecord(mNewVoiceMailTag, mNewVoiceMailNum); - if (mMailboxIndex != 0 && mMailboxIndex != 0xff) { new AdnRecordLoader(mFh).updateEF(adn, EF_MBDN, EF_EXT6, mMailboxIndex, null, - obtainMessage(EVENT_SET_MBDN_DONE, onComplete)); + obtainMessage(EVENT_SET_MBDN_DONE, AdnRecordLoader.VOICEMAIL_ALPHATAG_ARG, + 0 /* ignored arg2 */, onComplete)); } else if (isCphsMailboxEnabled()) { new AdnRecordLoader(mFh).updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null, - obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete)); + obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, + AdnRecordLoader.VOICEMAIL_ALPHATAG_ARG, + 0 /* ignored arg2 */, onComplete)); } else { AsyncResult.forMessage((onComplete)).exception = @@ -844,7 +858,7 @@ public class SIMRecords extends IccRecords { mIccId = IccUtils.bcdToString(data, 0, data.length); mFullIccId = IccUtils.bchToString(data, 0, data.length); - log("iccid: " + SubscriptionInfo.givePrintableIccid(mFullIccId)); + log("iccid: " + SubscriptionInfo.getPrintableId(mFullIccId)); break; case EVENT_GET_AD_DONE: @@ -1021,10 +1035,20 @@ public class SIMRecords extends IccRecords { if (DBG) log("EVENT_SET_MBDN_DONE ex:" + ar.exception); if (ar.exception == null) { + /** + * Check for any changes made to voicemail alphaTag while saving to SIM. + * if voicemail alphaTag length is more than allowed limit of SIM EF then + * null alphaTag will be saved to SIM {@code AdnRecordLoader}. + */ + if (ar.result != null) { + AdnRecord adnRecord = (AdnRecord) (ar.result); + if (adnRecord != null) { + mNewVoiceMailTag = adnRecord.mAlphaTag; + } + } mVoiceMailNum = mNewVoiceMailNum; mVoiceMailTag = mNewVoiceMailTag; } - if (isCphsMailboxEnabled()) { adn = new AdnRecord(mVoiceMailTag, mVoiceMailNum); Message onCphsCompleted = (Message) ar.userObj; @@ -1048,7 +1072,8 @@ public class SIMRecords extends IccRecords { new AdnRecordLoader(mFh) .updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null, obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, - onCphsCompleted)); + AdnRecordLoader.VOICEMAIL_ALPHATAG_ARG, + 0 /* ignored arg2 */, onCphsCompleted)); } else { if (ar.userObj != null) { CarrierConfigManager configManager = (CarrierConfigManager) @@ -1080,6 +1105,12 @@ public class SIMRecords extends IccRecords { isRecordLoadResponse = false; ar = (AsyncResult) msg.obj; if (ar.exception == null) { + if (ar.result != null) { + AdnRecord adnRecord = (AdnRecord) (ar.result); + if (adnRecord != null) { + mNewVoiceMailTag = adnRecord.mAlphaTag; + } + } mVoiceMailNum = mNewVoiceMailNum; mVoiceMailTag = mNewVoiceMailTag; } else { @@ -1328,15 +1359,6 @@ public class SIMRecords extends IccRecords { } break; - case EVENT_GET_FDN_DONE: - ar = (AsyncResult) msg.obj; - if (ar.exception != null) { - loge("Failed to read USIM EF_FDN field error=" + ar.exception); - } else { - log("EF_FDN read successfully"); - } - break; - default: super.handleMessage(msg); // IccRecords handles generic record load responses } @@ -2162,13 +2184,9 @@ public class SIMRecords extends IccRecords { log("[CSP] Value Added Service Group (0xC0), not found!"); } - public void loadFdnRecords() { - if (mParentApp != null && mParentApp.getIccFdnEnabled() - && mParentApp.getIccFdnAvailable()) { - log("Loading FdnRecords"); - mAdnCache.requestLoadAllAdnLike(IccConstants.EF_FDN, getExtFromEf(IccConstants.EF_FDN), - obtainMessage(EVENT_GET_FDN_DONE)); - } + @VisibleForTesting + public void setMailboxIndex(int mailboxIndex) { + mMailboxIndex = mailboxIndex; } @Override diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java index 689e4b7be930682e1b1d7d9cb98da27f71080cc4..960a166c4985f20fd75dcbe51c2af012a73f5f89 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccCard.java +++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java @@ -21,16 +21,20 @@ import android.content.Context; import android.os.Build; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.IndentingPrintWriter; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.uicc.IccCardStatus.CardState; +import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; import com.android.internal.telephony.uicc.euicc.EuiccCard; import com.android.internal.telephony.uicc.euicc.EuiccPort; +import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; +import java.util.LinkedHashMap; /** * {@hide} @@ -49,17 +53,17 @@ public class UiccCard { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private CardState mCardState; protected String mCardId; - protected boolean mIsSupportsMultipleEnabledProfiles; + protected MultipleEnabledProfilesMode mSupportedMepMode; - protected HashMap mUiccPorts = new HashMap<>(); + protected LinkedHashMap mUiccPorts = new LinkedHashMap<>(); private HashMap mPhoneIdToPortIdx = new HashMap<>(); public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock, - boolean isSupportsMultipleEnabledProfiles) { + MultipleEnabledProfilesMode supportedMepMode) { if (DBG) log("Creating"); mCardState = ics.mCardState; mLock = lock; - mIsSupportsMultipleEnabledProfiles = isSupportsMultipleEnabledProfiles; + mSupportedMepMode = supportedMepMode; update(c, ci, ics, phoneId); } @@ -109,7 +113,7 @@ public class UiccCard { if (port == null) { if (this instanceof EuiccCard) { port = new EuiccPort(c, ci, ics, phoneId, mLock, this, - mIsSupportsMultipleEnabledProfiles); // eSim + mSupportedMepMode); // eSim } else { port = new UiccPort(c, ci, ics, phoneId, mLock, this); // pSim } @@ -143,13 +147,13 @@ public class UiccCard { /** - * Updates MEP(Multiple Enabled Profile) support flag. + * Updates MEP(Multiple Enabled Profile) supported mode flag. * *

If IccSlotStatus comes later, the number of ports reported is only known after the * UiccCard creation which will impact UICC MEP capability. */ - public void updateSupportMultipleEnabledProfile(boolean supported) { - mIsSupportsMultipleEnabledProfiles = supported; + public void updateSupportedMepMode(MultipleEnabledProfilesMode supportedMepMode) { + mSupportedMepMode = supportedMepMode; } @UnsupportedAppUsage @@ -167,8 +171,11 @@ public class UiccCard { if (!TextUtils.isEmpty(mCardId)) { return mCardId; } else { - UiccProfile uiccProfile = mUiccPorts.get(TelephonyManager.DEFAULT_PORT_INDEX) - .getUiccProfile(); + UiccPort uiccPort = mUiccPorts.get(TelephonyManager.DEFAULT_PORT_INDEX); + if (uiccPort == null) { + return null; + } + UiccProfile uiccProfile = uiccPort.getUiccProfile(); return uiccProfile == null ? null : uiccProfile.getIccId(); } } @@ -210,15 +217,20 @@ public class UiccCard { Rlog.e(LOG_TAG, msg); } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); pw.println("UiccCard:"); - pw.println(" mCardState=" + mCardState); - pw.println(" mCardId=" + mCardId); - pw.println(" mNumberOfPorts=" + mUiccPorts.size()); - pw.println( "mIsSupportsMultipleEnabledProfiles=" + mIsSupportsMultipleEnabledProfiles); - pw.println(); + pw.increaseIndent(); + pw.println("mCardState=" + mCardState); + pw.println("mCardId=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mCardId)); + pw.println("mNumberOfPorts=" + mUiccPorts.size()); + pw.println("mSupportedMepMode=" + mSupportedMepMode); + pw.println("mUiccPorts= size=" + mUiccPorts.size()); + pw.increaseIndent(); for (UiccPort uiccPort : mUiccPorts.values()) { uiccPort.dump(fd, pw, args); } + pw.decreaseIndent(); + pw.decreaseIndent(); } } diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java index 9959d9bb2bad24e1e9fb8926f89ab734ccbc4ee5..fe19e991cbdcf0ba8b993813d9ce32bf21a7d438 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java +++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java @@ -60,6 +60,9 @@ public class UiccCardApplication { */ public static final int AUTH_CONTEXT_EAP_SIM = PhoneConstants.AUTH_CONTEXT_EAP_SIM; public static final int AUTH_CONTEXT_EAP_AKA = PhoneConstants.AUTH_CONTEXT_EAP_AKA; + public static final int AUTH_CONTEXT_GBA_BOOTSTRAP = PhoneConstants.AUTH_CONTEXT_GBA_BOOTSTRAP; + public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL = + PhoneConstants.AUTHTYPE_GBA_NAF_KEY_EXTERNAL; public static final int AUTH_CONTEXT_UNDEFINED = PhoneConstants.AUTH_CONTEXT_UNDEFINED; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -268,7 +271,7 @@ public class UiccCardApplication { loge("Bogus facility lock response"); } if (mIccFdnEnabled && mIccFdnAvailable) { - ((SIMRecords) mIccRecords).loadFdnRecords(); + mIccRecords.loadFdnRecords(); } } } diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java index ec4a51bc43669956bbd55999c8902e87a33097b2..596ccf62bb5c137b57288793472fc4054c684b02 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java +++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java @@ -427,8 +427,8 @@ public class UiccCarrierPrivilegeRules extends Handler { if (ar.exception == null && ar.result != null) { mChannelId = ((int[]) ar.result)[0]; mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3, - DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId, - mAIDInUse)); + DATA, false /*isEs10Command*/, obtainMessage( + EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId, mAIDInUse)); } else { if (shouldRetry(ar, mRetryCount)) { log("should retry"); @@ -484,7 +484,7 @@ public class UiccCarrierPrivilegeRules extends Handler { } } else { mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, - P1, P2_EXTENDED_DATA, P3, DATA, + P1, P2_EXTENDED_DATA, P3, DATA, false /*isEs10Command*/, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId, mAIDInUse)); break; @@ -519,7 +519,7 @@ public class UiccCarrierPrivilegeRules extends Handler { } } - mUiccProfile.iccCloseLogicalChannel(mChannelId, obtainMessage( + mUiccProfile.iccCloseLogicalChannel(mChannelId, false /*isEs10*/, obtainMessage( EVENT_CLOSE_LOGICAL_CHANNEL_DONE, 0, mAIDInUse)); mChannelId = -1; break; diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java index 6de9a65fdbf40c54c06972c9b997261bf303feb8..945646735503cae80e814e213117c211c98559a0 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccController.java +++ b/src/java/com/android/internal/telephony/uicc/UiccController.java @@ -34,12 +34,12 @@ import android.os.AsyncResult; import android.os.Build; import android.os.Handler; import android.os.Message; -import android.os.Registrant; import android.os.RegistrantList; import android.preference.PreferenceManager; import android.sysprop.TelephonyProperties; import android.telephony.AnomalyReporter; import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.TelephonyManager.SimState; @@ -48,6 +48,7 @@ import android.telephony.UiccPortInfo; import android.telephony.UiccSlotMapping; import android.telephony.data.ApnSetting; import android.text.TextUtils; +import android.util.IndentingPrintWriter; import android.util.LocalLog; import android.util.Log; @@ -62,11 +63,11 @@ import com.android.internal.telephony.PhoneConfigurationManager; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.RadioConfig; -import com.android.internal.telephony.SubscriptionInfoUpdater; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.euicc.EuiccCard; +import com.android.internal.telephony.util.ArrayUtils; import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; @@ -76,6 +77,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import java.util.stream.IntStream; /** @@ -741,38 +743,6 @@ public class UiccController extends Handler { } } - static void updateInternalIccStateForInactivePort( - Context context, int prevActivePhoneId, String iccId) { - if (SubscriptionManager.isValidPhoneId(prevActivePhoneId)) { - // Mark SIM state as ABSENT on previously phoneId. - TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); - telephonyManager.setSimStateForPhone(prevActivePhoneId, - IccCardConstants.State.ABSENT.toString()); - } - - SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater(); - if (subInfoUpdator != null) { - subInfoUpdator.updateInternalIccStateForInactivePort(prevActivePhoneId, iccId); - } else { - Rlog.e(LOG_TAG, "subInfoUpdate is null."); - } - } - - static void updateInternalIccState(Context context, IccCardConstants.State state, String reason, - int phoneId) { - TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); - telephonyManager.setSimStateForPhone(phoneId, state.toString()); - - SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater(); - if (subInfoUpdator != null) { - subInfoUpdator.updateInternalIccState(getIccStateIntentString(state), reason, phoneId); - } else { - Rlog.e(LOG_TAG, "subInfoUpdate is null."); - } - } - /** * Update SIM state for the inactive eSIM port. * @@ -989,7 +959,7 @@ public class UiccController extends Handler { log("updateSimState: phoneId=" + phoneId + ", state=" + state + ", reason=" + reason); if (!SubscriptionManager.isValidPhoneId(phoneId)) { - Rlog.e(LOG_TAG, "updateInternalIccState: Invalid phone id " + phoneId); + Rlog.e(LOG_TAG, "updateSimState: Invalid phone id " + phoneId); return; } @@ -1061,10 +1031,10 @@ public class UiccController extends Handler { IccCardStatus status = (IccCardStatus)ar.result; - logWithLocalLog("onGetIccCardStatusDone: phoneId " + index + " IccCardStatus: " + status); + logWithLocalLog("onGetIccCardStatusDone: phoneId-" + index + " IccCardStatus: " + status); int slotId = status.mSlotPortMapping.mPhysicalSlotIndex; - if (VDBG) log("onGetIccCardStatusDone: phoneId " + index + " physicalSlotIndex " + slotId); + if (VDBG) log("onGetIccCardStatusDone: phoneId-" + index + " physicalSlotIndex " + slotId); if (slotId == INVALID_SLOT_ID) { slotId = index; } @@ -1095,6 +1065,14 @@ public class UiccController extends Handler { return; } + UiccPort port = card.getUiccPort(status.mSlotPortMapping.mPortIndex); + if (port == null) { + if (DBG) log("mUiccSlots[" + slotId + "] has no UiccPort with index[" + + status.mSlotPortMapping.mPortIndex + "]. Notifying IccChangedRegistrants"); + mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null)); + return; + } + String cardString = null; boolean isEuicc = mUiccSlots[slotId].isEuicc(); if (isEuicc) { @@ -1756,6 +1734,18 @@ public class UiccController extends Handler { return mUseRemovableEsimAsDefault; } + /** + * Returns the MEP mode supported by the UiccSlot associated with slotIndex. + * @param slotIndex physical slot index + * @return MultipleEnabledProfilesMode supported by the slot + */ + public IccSlotStatus.MultipleEnabledProfilesMode getSupportedMepMode(int slotIndex) { + synchronized (mLock) { + UiccSlot slot = getUiccSlot(slotIndex); + return slot != null ? slot.getSupportedMepMode() + : IccSlotStatus.MultipleEnabledProfilesMode.NONE; + } + } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void log(String string) { @@ -1777,35 +1767,40 @@ public class UiccController extends Handler { sLocalLog.log(data); } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("UiccController: " + this); - pw.println(" mContext=" + mContext); - pw.println(" mInstance=" + mInstance); - pw.println(" mIccChangedRegistrants: size=" + mIccChangedRegistrants.size()); - for (int i = 0; i < mIccChangedRegistrants.size(); i++) { - pw.println(" mIccChangedRegistrants[" + i + "]=" - + ((Registrant)mIccChangedRegistrants.get(i)).getHandler()); + private List getPrintableCardStrings() { + if (!ArrayUtils.isEmpty(mCardStrings)) { + return mCardStrings.stream().map(SubscriptionInfo::getPrintableId).collect( + Collectors.toList()); } - pw.println(); - pw.flush(); - pw.println(" mIsCdmaSupported=" + isCdmaSupported(mContext)); - pw.println(" mHasBuiltInEuicc=" + mHasBuiltInEuicc); - pw.println(" mHasActiveBuiltInEuicc=" + mHasActiveBuiltInEuicc); - pw.println(" mCardStrings=" + mCardStrings); - pw.println(" mDefaultEuiccCardId=" + mDefaultEuiccCardId); - pw.println(" mPhoneIdToSlotId=" + Arrays.toString(mPhoneIdToSlotId)); - pw.println(" mUseRemovableEsimAsDefault=" + mUseRemovableEsimAsDefault); - pw.println(" mUiccSlots: size=" + mUiccSlots.length); + return mCardStrings; + } + + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + pw.println("mIsCdmaSupported=" + isCdmaSupported(mContext)); + pw.println("mHasBuiltInEuicc=" + mHasBuiltInEuicc); + pw.println("mHasActiveBuiltInEuicc=" + mHasActiveBuiltInEuicc); + pw.println("mCardStrings=" + getPrintableCardStrings()); + pw.println("mDefaultEuiccCardId=" + mDefaultEuiccCardId); + pw.println("mPhoneIdToSlotId=" + Arrays.toString(mPhoneIdToSlotId)); + pw.println("mUseRemovableEsimAsDefault=" + mUseRemovableEsimAsDefault); + pw.println("mUiccSlots: size=" + mUiccSlots.length); + pw.increaseIndent(); for (int i = 0; i < mUiccSlots.length; i++) { if (mUiccSlots[i] == null) { - pw.println(" mUiccSlots[" + i + "]=null"); + pw.println("mUiccSlots[" + i + "]=null"); } else { - pw.println(" mUiccSlots[" + i + "]=" + mUiccSlots[i]); + pw.println("mUiccSlots[" + i + "]:"); + pw.increaseIndent(); mUiccSlots[i].dump(fd, pw, args); + pw.decreaseIndent(); } } - pw.println(" sLocalLog= "); - sLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); + pw.println(); + pw.println("sLocalLog= "); + pw.increaseIndent(); mPinStorage.dump(fd, pw, args); + sLocalLog.dump(fd, pw, args); } } diff --git a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java index 9543908e715af0bb9d1e29c19ea05fe1162a81e6..be045c4c92b12e67e69d51c2a506c0b5a55ad9e7 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java +++ b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java @@ -81,7 +81,7 @@ public class UiccPkcs15 extends Handler { private void selectFile() { if (mChannelId >= 0) { mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xA4, 0x00, 0x04, 0x02, - mFileId, obtainMessage(EVENT_SELECT_FILE_DONE)); + mFileId, false /*isEs10Command*/, obtainMessage(EVENT_SELECT_FILE_DONE)); } else { log("EF based"); } @@ -90,7 +90,7 @@ public class UiccPkcs15 extends Handler { private void readBinary() { if (mChannelId >=0 ) { mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xB0, 0x00, 0x00, 0x00, - "", obtainMessage(EVENT_READ_BINARY_DONE)); + "", false /*isEs10Command*/, obtainMessage(EVENT_READ_BINARY_DONE)); } else { log("EF based"); } @@ -281,8 +281,8 @@ public class UiccPkcs15 extends Handler { private void cleanUp() { log("cleanUp"); if (mChannelId >= 0) { - mUiccProfile.iccCloseLogicalChannel(mChannelId, obtainMessage( - EVENT_CLOSE_LOGICAL_CHANNEL_DONE)); + mUiccProfile.iccCloseLogicalChannel(mChannelId, false /*isEs10*/, + obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE)); mChannelId = -1; } mLoadedCallback.sendToTarget(); diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java index ad00066c8e1fc29d4400a2d7c3b5469964748c40..d89eab1d86f636b194e50d5b35ad6a80e7dcc41a 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccPort.java +++ b/src/java/com/android/internal/telephony/uicc/UiccPort.java @@ -217,12 +217,12 @@ public class UiccPort { /** * Exposes {@link CommandsInterface#iccCloseLogicalChannel} * @deprecated Please use - * {@link UiccProfile#iccCloseLogicalChannel(int, Message)} instead. + * {@link UiccProfile#iccCloseLogicalChannel(int, boolean, Message)} instead. */ @Deprecated public void iccCloseLogicalChannel(int channel, Message response) { if (mUiccProfile != null) { - mUiccProfile.iccCloseLogicalChannel(channel, response); + mUiccProfile.iccCloseLogicalChannel(channel, false /*isEs10*/, response); } else { loge("iccCloseLogicalChannel Failed!"); } @@ -231,15 +231,15 @@ public class UiccPort { /** * Exposes {@link CommandsInterface#iccTransmitApduLogicalChannel} * @deprecated Please use {@link - * UiccProfile#iccTransmitApduLogicalChannel(int, int, int, int, int, int, String, Message)} - * instead. + * UiccProfile#iccTransmitApduLogicalChannel(int, int, int, int, int, int, String, + * boolean, Message)} instead. */ @Deprecated public void iccTransmitApduLogicalChannel(int channel, int cla, int command, int p1, int p2, int p3, String data, Message response) { if (mUiccProfile != null) { mUiccProfile.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3, - data, response); + data, false /*isEs10Command*/, response); } else { loge("iccTransmitApduLogicalChannel Failed!"); } @@ -365,7 +365,7 @@ public class UiccPort { pw.increaseIndent(); pw.println("mPortIdx=" + mPortIdx); pw.println("mCi=" + mCi); - pw.println("mIccid=" + SubscriptionInfo.givePrintableIccid(mIccid)); + pw.println("mIccid=" + SubscriptionInfo.getPrintableId(mIccid)); pw.println("mPhoneId=" + mPhoneId); pw.println("mPhysicalSlotIndex=" + mPhysicalSlotIndex); synchronized (mOpenChannelRecords) { diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java index 5809ff9b41b1573b2862c5586998c7ef5f5b458a..83db022f985e32e8ab2e01f31004c64ed99515ab 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java +++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java @@ -63,7 +63,6 @@ import com.android.internal.telephony.MccTable; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.cat.CatService; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; @@ -81,6 +80,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -544,25 +544,15 @@ public class UiccProfile extends IccCard { if (!TextUtils.isEmpty(iso) && !iso.equals(TelephonyManager.getSimCountryIsoForPhone(mPhoneId))) { mTelephonyManager.setSimCountryIsoForPhone(mPhoneId, iso); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().setCountryIso(subId, iso); - } else { - SubscriptionController.getInstance().setCountryIso(iso, subId); - } + SubscriptionManagerService.getInstance().setCountryIso(subId, iso); } } private void updateCarrierNameForSubscription(int subId, int nameSource) { /* update display name with carrier override */ - SubscriptionInfo subInfo; - - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - subInfo = SubscriptionManagerService.getInstance().getActiveSubscriptionInfo(subId, - mContext.getOpPackageName(), mContext.getAttributionTag()); - } else { - subInfo = SubscriptionController.getInstance().getActiveSubscriptionInfo( - subId, mContext.getOpPackageName(), mContext.getAttributionTag()); - } + SubscriptionInfo subInfo = SubscriptionManagerService.getInstance() + .getActiveSubscriptionInfo(subId, mContext.getOpPackageName(), + mContext.getAttributionTag()); if (subInfo == null) { return; @@ -573,13 +563,8 @@ public class UiccProfile extends IccCard { if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) { log("sim name[" + mPhoneId + "] = " + newCarrierName); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - SubscriptionManagerService.getInstance().setDisplayNameUsingSrc( - newCarrierName, subId, nameSource); - } else { - SubscriptionController.getInstance().setDisplayNameUsingSrc( - newCarrierName, subId, nameSource); - } + SubscriptionManagerService.getInstance().setDisplayNameUsingSrc( + newCarrierName, subId, nameSource); } } @@ -836,13 +821,8 @@ public class UiccProfile extends IccCard { } log("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState); - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - UiccController.getInstance().updateSimState(mPhoneId, mExternalState, - getIccStateReason(mExternalState)); - } else { - UiccController.updateInternalIccState(mContext, mExternalState, - getIccStateReason(mExternalState), mPhoneId); - } + UiccController.getInstance().updateSimState(mPhoneId, mExternalState, + getIccStateReason(mExternalState)); } } @@ -1443,7 +1423,7 @@ public class UiccProfile extends IccCard { Set uninstalledCarrierPackages = new ArraySet<>(); List accessRules = rules.getAccessRules(); for (UiccAccessRule accessRule : accessRules) { - String certHexString = accessRule.getCertificateHexString().toUpperCase(); + String certHexString = accessRule.getCertificateHexString().toUpperCase(Locale.ROOT); String pkgName = certPackageMap.get(certHexString); if (!TextUtils.isEmpty(pkgName) && !isPackageBundled(mContext, pkgName)) { uninstalledCarrierPackages.add(pkgName); @@ -1473,7 +1453,7 @@ public class UiccProfile extends IccCard { String[] keyValue = keyValueString.split(keyValueDelim); if (keyValue.length == 2) { - map.put(keyValue[0].toUpperCase(), keyValue[1]); + map.put(keyValue[0].toUpperCase(Locale.ROOT), keyValue[1]); } else { loge("Incorrect length of key-value pair in carrier app allow list map. " + "Length should be exactly 2"); @@ -1633,9 +1613,9 @@ public class UiccProfile extends IccCard { /** * Exposes {@link CommandsInterface#iccCloseLogicalChannel} */ - public void iccCloseLogicalChannel(int channel, Message response) { + public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) { logWithLocalLog("iccCloseLogicalChannel: " + channel); - mCi.iccCloseLogicalChannel(channel, + mCi.iccCloseLogicalChannel(channel, isEs10, mHandler.obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE, response)); } @@ -1643,9 +1623,9 @@ public class UiccProfile extends IccCard { * Exposes {@link CommandsInterface#iccTransmitApduLogicalChannel} */ public void iccTransmitApduLogicalChannel(int channel, int cla, int command, - int p1, int p2, int p3, String data, Message response) { - mCi.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3, - data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response)); + int p1, int p2, int p3, String data, boolean isEs10Command, Message response) { + mCi.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3, data, isEs10Command, + mHandler.obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response)); } /** @@ -1739,42 +1719,34 @@ public class UiccProfile extends IccCard { */ public boolean setOperatorBrandOverride(String brand) { log("setOperatorBrandOverride: " + brand); - log("current iccId: " + SubscriptionInfo.givePrintableIccid(getIccId())); + log("current iccId: " + SubscriptionInfo.getPrintableId(getIccId())); String iccId = getIccId(); if (TextUtils.isEmpty(iccId)) { return false; } - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - int subId = SubscriptionManager.getSubscriptionId(getPhoneId()); - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(subId); - if (subInfo == null) { - loge("setOperatorBrandOverride: Cannot find subscription info for sub " + subId); - return false; - } + int subId = SubscriptionManager.getSubscriptionId(getPhoneId()); + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(subId); + if (subInfo == null) { + loge("setOperatorBrandOverride: Cannot find subscription info for sub " + subId); + return false; + } - List subInfos = new ArrayList<>(); - subInfos.add(subInfo.toSubscriptionInfo()); - String groupUuid = subInfo.getGroupUuid(); - if (!TextUtils.isEmpty(groupUuid)) { - subInfos.addAll(SubscriptionManagerService.getInstance() - .getSubscriptionsInGroup(ParcelUuid.fromString(groupUuid), - mContext.getOpPackageName(), mContext.getFeatureId())); - } + List subInfos = new ArrayList<>(); + subInfos.add(subInfo.toSubscriptionInfo()); + String groupUuid = subInfo.getGroupUuid(); + if (!TextUtils.isEmpty(groupUuid)) { + subInfos.addAll(SubscriptionManagerService.getInstance() + .getSubscriptionsInGroup(ParcelUuid.fromString(groupUuid), + mContext.getOpPackageName(), mContext.getFeatureId())); + } - if (subInfos.stream().noneMatch(info -> TextUtils.equals(IccUtils.stripTrailingFs( - info.getIccId()), IccUtils.stripTrailingFs(iccId)))) { - loge("iccId doesn't match current active subId."); - return false; - } - } else { - if (!SubscriptionController.getInstance().checkPhoneIdAndIccIdMatch( - getPhoneId(), iccId)) { - loge("iccId doesn't match current active subId."); - return false; - } + if (subInfos.stream().noneMatch(info -> TextUtils.equals(IccUtils.stripTrailingFs( + info.getIccId()), IccUtils.stripTrailingFs(iccId)))) { + loge("iccId doesn't match current active subId."); + return false; } SharedPreferences.Editor spEditor = diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java index 2490efa34456b66395ab621f6d1149e69277641e..db10271a2ffbcf1b3962763720b651097a54554f 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java +++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java @@ -33,6 +33,8 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.IndentingPrintWriter; +import android.util.Log; import android.view.WindowManager; import com.android.internal.R; @@ -41,7 +43,9 @@ import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.uicc.IccCardStatus.CardState; +import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; import com.android.internal.telephony.uicc.euicc.EuiccCard; +import com.android.internal.telephony.util.ArrayUtils; import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; @@ -79,7 +83,6 @@ public class UiccSlot extends Handler { private final Object mLock = new Object(); private boolean mActive; private boolean mStateIsUnknown = true; - private CardState mCardState; private Context mContext; private UiccCard mUiccCard; private boolean mIsEuicc; @@ -87,12 +90,17 @@ public class UiccSlot extends Handler { private String mEid; private AnswerToReset mAtr; private boolean mIsRemovable; + private MultipleEnabledProfilesMode mSupportedMepMode; + // Map each available portIdx to phoneId private HashMap mPortIdxToPhoneId = new HashMap<>(); //Map each available portIdx with old radio state for state checking private HashMap mLastRadioState = new HashMap<>(); // Store iccId of each port. private HashMap mIccIds = new HashMap<>(); + // IccCardStatus and IccSlotStatus events order is not guaranteed. Inorder to handle MEP mode, + // map each available portIdx with CardState for card state checking + private HashMap mCardState = new HashMap<>(); private static final int EVENT_CARD_REMOVED = 13; private static final int EVENT_CARD_ADDED = 14; @@ -101,28 +109,31 @@ public class UiccSlot extends Handler { if (DBG) log("Creating"); mContext = c; mActive = isActive; - mCardState = null; + mSupportedMepMode = MultipleEnabledProfilesMode.NONE; } /** * Update slot. The main trigger for this is a change in the ICC Card status. */ public void update(CommandsInterface ci, IccCardStatus ics, int phoneId, int slotIndex) { - if (DBG) log("cardStatus update: " + ics.toString()); synchronized (mLock) { mPortIdxToPhoneId.put(ics.mSlotPortMapping.mPortIndex, phoneId); - CardState oldState = mCardState; - mCardState = ics.mCardState; + CardState oldState = mCardState.get(ics.mSlotPortMapping.mPortIndex); + mCardState.put(ics.mSlotPortMapping.mPortIndex, ics.mCardState); mIccIds.put(ics.mSlotPortMapping.mPortIndex, ics.iccid); parseAtr(ics.atr); mIsRemovable = isSlotRemovable(slotIndex); + // Update supported MEP mode in IccCardStatus if the CardState is present. + if (ics.mCardState.isCardPresent()) { + updateSupportedMepMode(ics.mSupportedMepMode); + } int radioState = ci.getRadioState(); if (DBG) { log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState); } - if (absentStateUpdateNeeded(oldState)) { + if (absentStateUpdateNeeded(oldState, ics.mSlotPortMapping.mPortIndex)) { updateCardStateAbsent(ci.getRadioState(), phoneId, ics.mSlotPortMapping.mPortIndex); // Because mUiccCard may be updated in both IccCardStatus and IccSlotStatus, we need to @@ -130,7 +141,8 @@ public class UiccSlot extends Handler { // 1. mCardState is changing from ABSENT to non ABSENT. // 2. The latest mCardState is not ABSENT, but there is no UiccCard instance. } else if ((oldState == null || oldState == CardState.CARDSTATE_ABSENT - || mUiccCard == null) && mCardState != CardState.CARDSTATE_ABSENT) { + || mUiccCard == null) && mCardState.get(ics.mSlotPortMapping.mPortIndex) + != CardState.CARDSTATE_ABSENT) { // No notification while we are just powering up if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE && mLastRadioState.getOrDefault(ics.mSlotPortMapping.mPortIndex, @@ -141,14 +153,17 @@ public class UiccSlot extends Handler { } // card is present in the slot now; create new mUiccCard - if (mUiccCard != null) { + if (mUiccCard != null && (!mIsEuicc + || ArrayUtils.isEmpty(mUiccCard.getUiccPortList()))) { loge("update: mUiccCard != null when card was present; disposing it now"); mUiccCard.dispose(); + mUiccCard = null; } if (!mIsEuicc) { // Uicc does not support MEP, passing false by default. - mUiccCard = new UiccCard(mContext, ci, ics, phoneId, mLock, false); + mUiccCard = new UiccCard(mContext, ci, ics, phoneId, mLock, + MultipleEnabledProfilesMode.NONE); } else { // The EID should be reported with the card status, but in case it's not we want // to catch that here @@ -156,8 +171,14 @@ public class UiccSlot extends Handler { loge("update: eid is missing. ics.eid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, ics.eid)); } - mUiccCard = new EuiccCard(mContext, ci, ics, phoneId, mLock, - isMultipleEnabledProfileSupported()); + if (mUiccCard == null) { + mUiccCard = new EuiccCard(mContext, ci, ics, phoneId, mLock, + getSupportedMepMode()); + } else { + // In MEP case, UiccCard instance is already created, just call update API. + // UiccPort initialization is handled inside UiccCard. + mUiccCard.update(mContext, ci, ics, phoneId); + } } } else { if (mUiccCard != null) { @@ -172,37 +193,30 @@ public class UiccSlot extends Handler { * Update slot based on IccSlotStatus. */ public void update(CommandsInterface[] ci, IccSlotStatus iss, int slotIndex) { - if (DBG) log("slotStatus update: " + iss.toString()); synchronized (mLock) { IccSimPortInfo[] simPortInfos = iss.mSimPortInfos; - CardState oldState = mCardState; parseAtr(iss.atr); - mCardState = iss.cardState; mEid = iss.eid; mIsRemovable = isSlotRemovable(slotIndex); for (int i = 0; i < simPortInfos.length; i++) { int phoneId = iss.mSimPortInfos[i].mLogicalSlotIndex; + CardState oldState = mCardState.get(i); + mCardState.put(i, iss.cardState); mIccIds.put(i, simPortInfos[i].mIccId); if (!iss.mSimPortInfos[i].mPortActive) { // TODO: (b/79432584) evaluate whether should broadcast card state change // even if it's inactive. - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - UiccController.getInstance().updateSimStateForInactivePort( - mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID), - iss.mSimPortInfos[i].mIccId); - } else { - UiccController.updateInternalIccStateForInactivePort(mContext, - mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID), - iss.mSimPortInfos[i].mIccId); - } + UiccController.getInstance().updateSimStateForInactivePort( + mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID), + iss.mSimPortInfos[i].mIccId); mLastRadioState.put(i, TelephonyManager.RADIO_POWER_UNAVAILABLE); if (mUiccCard != null) { // Dispose the port mUiccCard.disposePort(i); } } else { - if (absentStateUpdateNeeded(oldState)) { + if (absentStateUpdateNeeded(oldState, i)) { int radioState = SubscriptionManager.isValidPhoneId(phoneId) ? ci[phoneId].getRadioState() : TelephonyManager.RADIO_POWER_UNAVAILABLE; @@ -229,10 +243,28 @@ public class UiccSlot extends Handler { mPortIdxToPhoneId.put(i, simPortInfos[i].mPortActive ? simPortInfos[i].mLogicalSlotIndex : INVALID_PHONE_ID); } - // Since the MEP capability is related with number ports reported, thus need to + updateSupportedMepMode(iss.mSupportedMepMode); + // Since the MEP capability is related to supported MEP mode, thus need to // update the flag after UiccCard creation. if (mUiccCard != null) { - mUiccCard.updateSupportMultipleEnabledProfile(isMultipleEnabledProfileSupported()); + mUiccCard.updateSupportedMepMode(getSupportedMepMode()); + } + } + } + + private void updateSupportedMepMode(MultipleEnabledProfilesMode mode) { + mSupportedMepMode = mode; + // If SupportedMepMode is MultipleEnabledProfilesMode.NONE, validate ATR and + // num of ports to handle backward compatibility for < RADIO_HAL_VERSION_2_1. + if (mode == MultipleEnabledProfilesMode.NONE) { + // Even ATR suggest UICC supports multiple enabled profiles, MEP can be disabled per + // carrier restrictions, so checking the real number of ports reported from modem is + // necessary. + if (mPortIdxToPhoneId.size() > 1 + && mAtr != null && mAtr.isMultipleEnabledProfilesSupported()) { + // Set MEP-B mode in case if modem sends wrong mode even though supports MEP. + Log.i(TAG, "Modem does not send proper supported MEP mode or older HAL version"); + mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; } } } @@ -306,16 +338,14 @@ public class UiccSlot extends Handler { /* Returns true if multiple enabled profiles are supported */ public boolean isMultipleEnabledProfileSupported() { - // even ATR suggest UICC supports multiple enabled profiles, MEP can be disabled per - // carrier restrictions, so checking the real number of ports reported from modem is - // necessary. - return mPortIdxToPhoneId.size() > 1 && mAtr != null && - mAtr.isMultipleEnabledProfilesSupported(); + synchronized (mLock) { + return mSupportedMepMode.isMepMode(); + } } - private boolean absentStateUpdateNeeded(CardState oldState) { + private boolean absentStateUpdateNeeded(CardState oldState, int portIndex) { return (oldState != CardState.CARDSTATE_ABSENT || mUiccCard != null) - && mCardState == CardState.CARDSTATE_ABSENT; + && mCardState.get(portIndex) == CardState.CARDSTATE_ABSENT; } private void updateCardStateAbsent(int radioState, int phoneId, int portIndex) { @@ -328,15 +358,12 @@ public class UiccSlot extends Handler { sendMessage(obtainMessage(EVENT_CARD_REMOVED, null)); } - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - UiccController.getInstance().updateSimState(phoneId, IccCardConstants.State.ABSENT, - null); - } else { - UiccController.updateInternalIccState(mContext, IccCardConstants.State.ABSENT, - null, phoneId); - } - // no card present in the slot now; dispose card and make mUiccCard null - nullifyUiccCard(false /* sim state is not unknown */); + UiccController.getInstance().updateSimState(phoneId, IccCardConstants.State.ABSENT, null); + // no card present in the slot now; dispose port and then card if needed. + disposeUiccCardIfNeeded(false /* sim state is not unknown */, portIndex); + // If SLOT_STATUS is the last event, wrong subscription is getting invalidate during + // slot switch event. To avoid it, reset the phoneId corresponding to the portIndex. + mPortIdxToPhoneId.put(portIndex, INVALID_PHONE_ID); mLastRadioState.put(portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE); } @@ -351,8 +378,23 @@ public class UiccSlot extends Handler { mUiccCard = null; } + private void disposeUiccCardIfNeeded(boolean isStateUnknown, int portIndex) { + if (mUiccCard != null) { + // First dispose UiccPort corresponding to the portIndex + mUiccCard.disposePort(portIndex); + if (ArrayUtils.isEmpty(mUiccCard.getUiccPortList())) { + // No UiccPort objects are found, safe to dispose the card + nullifyUiccCard(isStateUnknown); + } + } else { + mStateIsUnknown = isStateUnknown; + } + } + public boolean isStateUnknown() { - if (mCardState == null || mCardState == CardState.CARDSTATE_ABSENT) { + // CardState is not specific to any port index, use default port. + CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX); + if (cardState == null || cardState == CardState.CARDSTATE_ABSENT) { // mStateIsUnknown is valid only in this scenario. return mStateIsUnknown; } @@ -563,11 +605,9 @@ public class UiccSlot extends Handler { */ public CardState getCardState() { synchronized (mLock) { - if (mCardState == null) { - return CardState.CARDSTATE_ABSENT; - } else { - return mCardState; - } + // CardState is not specific to any port index, use default port. + CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX); + return cardState == null ? CardState.CARDSTATE_ABSENT : cardState; } } @@ -580,25 +620,28 @@ public class UiccSlot extends Handler { } } + /** + * Returns the supported MEP mode. + */ + public MultipleEnabledProfilesMode getSupportedMepMode() { + synchronized (mLock) { + return mSupportedMepMode; + } + } /** * Processes radio state unavailable event */ public void onRadioStateUnavailable(int phoneId) { - nullifyUiccCard(true /* sim state is unknown */); + int portIndex = getPortIndexFromPhoneId(phoneId); + disposeUiccCardIfNeeded(true /* sim state is unknown */, portIndex); if (phoneId != INVALID_PHONE_ID) { - if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { - UiccController.getInstance().updateSimState(phoneId, - IccCardConstants.State.UNKNOWN, null); - } else { - UiccController.updateInternalIccState( - mContext, IccCardConstants.State.UNKNOWN, null, phoneId); - } - mLastRadioState.put(getPortIndexFromPhoneId(phoneId), - TelephonyManager.RADIO_POWER_UNAVAILABLE); + UiccController.getInstance().updateSimState(phoneId, + IccCardConstants.State.UNKNOWN, null); } - - mCardState = null; + mLastRadioState.put(portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE); + // Reset CardState + mCardState.put(portIndex, null); } private void log(String msg) { @@ -612,32 +655,41 @@ public class UiccSlot extends Handler { private Map getPrintableIccIds() { Map printableIccIds = mIccIds.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, - e -> SubscriptionInfo.givePrintableIccid(e.getValue()))); + e -> SubscriptionInfo.getPrintableId(e.getValue()))); return printableIccIds; } /** * Dump */ - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("UiccSlot:"); - pw.println(" mActive=" + mActive); - pw.println(" mIsEuicc=" + mIsEuicc); - pw.println(" isEuiccSupportsMultipleEnabledProfiles=" - + isMultipleEnabledProfileSupported()); - pw.println(" mIsRemovable=" + mIsRemovable); - pw.println(" mLastRadioState=" + mLastRadioState); - pw.println(" mIccIds=" + getPrintableIccIds()); - pw.println(" mPortIdxToPhoneId=" + mPortIdxToPhoneId); - pw.println(" mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid)); - pw.println(" mCardState=" + mCardState); + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + pw.println("mActive=" + mActive); + pw.println("mIsEuicc=" + mIsEuicc); + pw.println("isEuiccSupportsMultipleEnabledProfiles=" + isMultipleEnabledProfileSupported()); + pw.println("mIsRemovable=" + mIsRemovable); + pw.println("mLastRadioState=" + mLastRadioState); + pw.println("mIccIds=" + getPrintableIccIds()); + pw.println("mPortIdxToPhoneId=" + mPortIdxToPhoneId); + pw.println("mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid)); + pw.println("mCardState=" + mCardState); + pw.println("mSupportedMepMode=" + mSupportedMepMode); if (mUiccCard != null) { - pw.println(" mUiccCard=" + mUiccCard); + pw.println("mUiccCard="); mUiccCard.dump(fd, pw, args); } else { - pw.println(" mUiccCard=null"); + pw.println("mUiccCard=null"); } pw.println(); pw.flush(); } + + @NonNull + @Override + public String toString() { + return "[UiccSlot: mActive=" + mActive + ", mIccId=" + getPrintableIccIds() + ", mIsEuicc=" + + mIsEuicc + ", MEP=" + isMultipleEnabledProfileSupported() + ", mPortIdxToPhoneId=" + + mPortIdxToPhoneId + ", mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid) + + ", mCardState=" + mCardState + " mSupportedMepMode=" + mSupportedMepMode + "]"; + } } diff --git a/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java b/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java old mode 100755 new mode 100644 diff --git a/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java b/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java index fc58d3cec3744124d344b27b2f1ef2b7f3c3c0d3..ea2bf42dcee87227962ec1c5fa26bb2b2cd7dd02 100644 --- a/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java +++ b/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java @@ -157,4 +157,8 @@ public final class UsimServiceTable extends IccServiceTable { protected Object[] getValues() { return UsimService.values(); } + + public byte[] getUSIMServiceTable() { + return mServiceTable; + } } diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java index fc74d293e0c141d77084ef41c28fc469bed9b6e5..698fbc834c1a45a6c6f155fdcf8fc2ecb0f5acb1 100644 --- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java +++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java @@ -29,6 +29,7 @@ import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.uicc.IccCardStatus; +import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; import com.android.internal.telephony.uicc.UiccCard; import com.android.internal.telephony.uicc.UiccPort; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; @@ -45,8 +46,8 @@ public class EuiccCard extends UiccCard { private RegistrantList mEidReadyRegistrants; public EuiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock, - boolean isSupportsMultipleEnabledProfiles) { - super(c, ci, ics, phoneId, lock, isSupportsMultipleEnabledProfiles); + MultipleEnabledProfilesMode supportedMepMode) { + super(c, ci, ics, phoneId, lock, supportedMepMode); if (TextUtils.isEmpty(ics.eid)) { loge("no eid given in constructor for phone " + phoneId); loadEidAndNotifyRegistrants(); @@ -57,17 +58,17 @@ public class EuiccCard extends UiccCard { } /** - * Updates MEP(Multiple Enabled Profile) support flag. + * Updates MEP(Multiple Enabled Profile) supported mode flag. * *

If IccSlotStatus comes later, the number of ports reported is only known after the - * UiccCard creation which will impact UICC MEP capability. + * UiccCard creation which will impact UICC MEP capability in case of old HAL version. */ @Override - public void updateSupportMultipleEnabledProfile(boolean supported) { - mIsSupportsMultipleEnabledProfiles = supported; + public void updateSupportedMepMode(MultipleEnabledProfilesMode supportedMepMode) { + mSupportedMepMode = supportedMepMode; for (UiccPort port : mUiccPorts.values()) { if (port instanceof EuiccPort) { - ((EuiccPort) port).updateSupportMultipleEnabledProfile(supported); + ((EuiccPort) port).updateSupportedMepMode(supportedMepMode); } else { loge("eUICC card has non-euicc port object:" + port.toString()); } diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java index 639915abcd3ed9b0ab4a52be6c10f1d7c4ab5c22..3bd66f8d876467d21caf9341b71431b25b896919 100644 --- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java +++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java @@ -28,6 +28,7 @@ import android.telephony.euicc.EuiccCardManager; import android.telephony.euicc.EuiccNotification; import android.telephony.euicc.EuiccRulesAuthTable; import android.text.TextUtils; +import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; @@ -35,7 +36,9 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.uicc.IccCardStatus; import com.android.internal.telephony.uicc.IccIoResult; +import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; import com.android.internal.telephony.uicc.IccUtils; +import com.android.internal.telephony.uicc.PortUtils; import com.android.internal.telephony.uicc.UiccCard; import com.android.internal.telephony.uicc.UiccPort; import com.android.internal.telephony.uicc.asn1.Asn1Decoder; @@ -101,9 +104,6 @@ public class EuiccPort extends UiccPort { private static final String DEV_CAP_NR5GC = "nr5gc"; private static final String DEV_CAP_EUTRAN5GC = "eutran5gc"; - private static final String ATR_ESIM_OS_V_M5 = - "3B9F97C00AB1FE453FC6838031E073FE211F65D002341569810F21"; - // These interfaces are used for simplifying the code by leveraging lambdas. private interface ApduRequestBuilder { void build(RequestBuilder requestBuilder) @@ -127,11 +127,10 @@ public class EuiccPort extends UiccPort { private EuiccSpecVersion mSpecVersion; private volatile String mEid; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public boolean mIsSupportsMultipleEnabledProfiles; - private String mAtr; + public MultipleEnabledProfilesMode mSupportedMepMode; public EuiccPort(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock, - UiccCard card, boolean isSupportsMultipleEnabledProfiles) { + UiccCard card, MultipleEnabledProfilesMode supportedMepMode) { super(c, ci, ics, phoneId, lock, card); // TODO: Set supportExtendedApdu based on ATR. mApduSender = new ApduSender(ci, ISD_R_AID, false /* supportExtendedApdu */); @@ -141,8 +140,7 @@ public class EuiccPort extends UiccPort { mEid = ics.eid; mCardId = ics.eid; } - mAtr = ics.atr; - mIsSupportsMultipleEnabledProfiles = isSupportsMultipleEnabledProfiles; + mSupportedMepMode = supportedMepMode; } /** @@ -165,18 +163,17 @@ public class EuiccPort extends UiccPort { if (!TextUtils.isEmpty(ics.eid)) { mEid = ics.eid; } - mAtr = ics.atr; super.update(c, ci, ics, uiccCard); } } /** - * Updates MEP(Multiple Enabled Profile) support flag. + * Updates MEP(Multiple Enabled Profile) supported mode flag. * The flag can be updated after the port creation. */ - public void updateSupportMultipleEnabledProfile(boolean supported) { - logd("updateSupportMultipleEnabledProfile"); - mIsSupportsMultipleEnabledProfiles = supported; + public void updateSupportedMepMode(MultipleEnabledProfilesMode supportedMepMode) { + logd("updateSupportedMepMode"); + mSupportedMepMode = supportedMepMode; } /** @@ -187,13 +184,8 @@ public class EuiccPort extends UiccPort { * @since 1.1.0 [GSMA SGP.22] */ public void getAllProfiles(AsyncResultCallback callback, Handler handler) { - byte[] profileTags; - if (mIsSupportsMultipleEnabledProfiles) { - profileTags = ATR_ESIM_OS_V_M5.equals(mAtr) - ? Tags.EUICC_PROFILE_MEP_TAGS : Tags.EUICC_PROFILE_MEP_TAGS_WITH_9F20; - } else { - profileTags = Tags.EUICC_PROFILE_TAGS; - } + byte[] profileTags = mSupportedMepMode.isMepMode() ? Tags.EUICC_PROFILE_MEP_TAGS + : Tags.EUICC_PROFILE_TAGS; sendApdu( newRequestProvider((RequestBuilder requestBuilder) -> requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_PROFILES) @@ -234,13 +226,8 @@ public class EuiccPort extends UiccPort { */ public final void getProfile(String iccid, AsyncResultCallback callback, Handler handler) { - byte[] profileTags; - if (mIsSupportsMultipleEnabledProfiles) { - profileTags = ATR_ESIM_OS_V_M5.equals(mAtr) - ? Tags.EUICC_PROFILE_MEP_TAGS : Tags.EUICC_PROFILE_MEP_TAGS_WITH_9F20; - } else { - profileTags = Tags.EUICC_PROFILE_TAGS; - } + byte[] profileTags = mSupportedMepMode.isMepMode() ? Tags.EUICC_PROFILE_MEP_TAGS + : Tags.EUICC_PROFILE_TAGS; sendApdu( newRequestProvider((RequestBuilder requestBuilder) -> requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_PROFILES) @@ -295,7 +282,7 @@ public class EuiccPort extends UiccPort { return null; case CODE_PROFILE_NOT_IN_EXPECTED_STATE: logd("Profile is already disabled, iccid: " - + SubscriptionInfo.givePrintableIccid(iccid)); + + SubscriptionInfo.getPrintableId(iccid)); return null; default: throw new EuiccCardErrorException( @@ -319,11 +306,20 @@ public class EuiccPort extends UiccPort { sendApduWithSimResetErrorWorkaround( newRequestProvider((RequestBuilder requestBuilder) -> { byte[] iccidBytes = IccUtils.bcdToBytes(padTrailingFs(iccid)); - requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_ENABLE_PROFILE) + Asn1Node.Builder builder = Asn1Node.newBuilder(Tags.TAG_ENABLE_PROFILE) .addChild(Asn1Node.newBuilder(Tags.TAG_CTX_COMP_0) .addChildAsBytes(Tags.TAG_ICCID, iccidBytes)) - .addChildAsBoolean(Tags.TAG_CTX_1, refresh) - .build().toHex()); + .addChildAsBoolean(Tags.TAG_CTX_1, refresh); + // Port index should be added only in case of MEP-A1 mode. + if (mSupportedMepMode.isMepA1Mode()) { + // In case of MEP-A1 and MEP-A2, profiles are selected on eSIM Ports 1 and + // higher (refer as target port). Hence, convert the portIndex to + // target port index before adding. + builder.addChildAsInteger(Tags.TAG_CTX_2, + PortUtils.convertToHalPortIndex(mSupportedMepMode, + super.getPortIdx())); + } + requestBuilder.addStoreData(builder.build().toHex()); }), response -> { int result; @@ -334,7 +330,7 @@ public class EuiccPort extends UiccPort { return null; case CODE_PROFILE_NOT_IN_EXPECTED_STATE: logd("Profile is already enabled, iccid: " - + SubscriptionInfo.givePrintableIccid(iccid)); + + SubscriptionInfo.getPrintableId(iccid)); return null; default: throw new EuiccCardErrorException( @@ -1262,10 +1258,8 @@ public class EuiccPort extends UiccPort { // if the Profile is in the Enabled state on the same eSIM Port as where this // getProfilesInfo command was sent. So should check for enabledOnEsimPort(TAG_PORT) // tag and verify its value is a valid port (means port value is >=0) or not. - if ((profileNode.hasChild(Tags.TAG_PORT) - && profileNode.getChild(Tags.TAG_PORT).asInteger() >= 0) - || (profileNode.hasChild(Tags.TAG_PORT_9F20) - && profileNode.getChild(Tags.TAG_PORT_9F20).asInteger() >= 0)) { + if (profileNode.hasChild(Tags.TAG_PORT) + && profileNode.getChild(Tags.TAG_PORT).asInteger() >= 0) { profileBuilder.setState(EuiccProfileInfo.PROFILE_STATE_ENABLED); } else { // noinspection WrongConstant @@ -1430,10 +1424,13 @@ public class EuiccPort extends UiccPort { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - super.dump(fd, pw, args); + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + super.dump(fd, printWriter, args); + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); pw.println("EuiccPort:"); - pw.println(" mEid=" + mEid); - pw.println(" mIsSupportsMultipleEnabledProfiles=" + mIsSupportsMultipleEnabledProfiles); + pw.increaseIndent(); + pw.println("mEid=" + mEid); + pw.println("mSupportedMepMode=" + mSupportedMepMode); + pw.decreaseIndent(); } } diff --git a/src/java/com/android/internal/telephony/uicc/euicc/Tags.java b/src/java/com/android/internal/telephony/uicc/euicc/Tags.java index 3aa9ef9de0bbf9f7dd3b623a49c1a22caf6e6b99..befc7f9034d3d9051ba85a1c9d453b0c1d8b23d3 100644 --- a/src/java/com/android/internal/telephony/uicc/euicc/Tags.java +++ b/src/java/com/android/internal/telephony/uicc/euicc/Tags.java @@ -84,10 +84,7 @@ class Tags { static final int TAG_PROFILE_NAME = 0x92; static final int TAG_OPERATOR_ID = 0xB7; static final int TAG_CARRIER_PRIVILEGE_RULES = 0xBF76; - // TODO: PORT TAG(9F20 OR 9F24) will be used based on ATR Strings because not all MEP capable - // devices have M5 OS. Once modem team is ready, revert back to 9F24 TAG only. static final int TAG_PORT = 0x9F24; - static final int TAG_PORT_9F20 = 0x9F20; // Tags from the RefArDo data standard - https://source.android.com/devices/tech/config/uicc static final int TAG_REF_AR_DO = 0xE2; @@ -129,22 +126,5 @@ class Tags { (byte) (TAG_PORT % 256), }; - // TAG list for Euicc Profile with 9F20 tag. - // TODO: This is temporary change, should be removed once all devices are upgraded to M5 OS. - static final byte[] EUICC_PROFILE_MEP_TAGS_WITH_9F20 = new byte[] { - TAG_ICCID, - (byte) TAG_NICKNAME, - (byte) TAG_SERVICE_PROVIDER_NAME, - (byte) TAG_PROFILE_NAME, - (byte) TAG_OPERATOR_ID, - (byte) (TAG_PROFILE_STATE / 256), - (byte) (TAG_PROFILE_STATE % 256), - (byte) TAG_PROFILE_CLASS, - (byte) TAG_PROFILE_POLICY_RULE, - (byte) (TAG_CARRIER_PRIVILEGE_RULES / 256), - (byte) (TAG_CARRIER_PRIVILEGE_RULES % 256), - (byte) (TAG_PORT_9F20 / 256), - (byte) (TAG_PORT_9F20 % 256), - }; private Tags() {} } diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java index 7a8978f82266d3d0bb658547e65fd69fe17e71ef..8fbeb4450fb389c7911e7cb645c6b77d35a25185 100644 --- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java +++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java @@ -43,6 +43,12 @@ class ApduCommand { /** Command data of an APDU as defined in GlobalPlatform Card Specification v.2.3. */ public final String cmdHex; + /** + * isEs10 indicates that the current streaming APDU contains an ES10 command or it is a regular + * APDU. (As per spec SGP.22 V3.0, ES10 commands needs to be sent over command port of MEP-A1) + */ + public final boolean isEs10; + /** The parameters are defined as in GlobalPlatform Card Specification v.2.3. */ ApduCommand(int channel, int cla, int ins, int p1, int p2, int p3, String cmdHex) { this.channel = channel; @@ -52,11 +58,14 @@ class ApduCommand { this.p2 = p2; this.p3 = p3; this.cmdHex = cmdHex; + // TODO: Currently ApduCommand is used for ES10 commands, so updating to true by default. + // Modify it in case used for non ES10 commands in future. + this.isEs10 = true; } @Override public String toString() { return "ApduCommand(channel=" + channel + ", cla=" + cla + ", ins=" + ins + ", p1=" + p1 - + ", p2=" + p2 + ", p3=" + p3 + ", cmd=" + cmdHex + ")"; + + ", p2=" + p2 + ", p3=" + p3 + ", cmd=" + cmdHex + ", isEs10=" + isEs10 + ")"; } } diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java index 82ddb80d8ed352b884088d36706ab29175b24e67..a69977b8f877fdf90686febc97b4c1f8d830a5db 100644 --- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java +++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java @@ -43,7 +43,10 @@ class CloseLogicalChannelInvocation extends AsyncMessageInvocation captor = ArgumentCaptor.forClass(IImsCallSession.class); - verify(mListener).onIncomingCall(captor.capture(), any()); + verify(mListener).onIncomingCall(captor.capture(), eq(null), any()); assertEquals(sessionBinder, captor.getValue()); } + @SmallTest + @Test + public void testNewIncomingCallReturnListener() throws Exception { + IImsCallSession sessionBinder = Mockito.mock(IImsCallSession.class); + ImsCallSessionImplBase session = new ImsCallSessionImplBase(); + session.setServiceImpl(sessionBinder); + String callId = "callID"; + Bundle extra = new Bundle(); + mFeature.incomingCall(session, callId, extra); + ArgumentCaptor captor = ArgumentCaptor.forClass(IImsCallSession.class); + verify(mListener).onIncomingCall(captor.capture(), eq(callId), eq(extra)); + assertEquals(sessionBinder, captor.getValue()); + } + @SmallTest @Test public void testSetTtyMessageMessenger() throws Exception { diff --git a/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java b/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java index 9ee1e30b70552a949969915b59429e301cad78aa..1269cc890456527bdf4e20a4adf3185e1df0c218 100644 --- a/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java +++ b/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java @@ -64,6 +64,11 @@ public class TestMmTelFeature extends MmTelFeature { notifyIncomingCall(c, new Bundle()); } + public ImsCallSessionListener incomingCall( + ImsCallSessionImplBase c, String callId, Bundle extra) { + return notifyIncomingCall(c, callId, extra); + } + @Override public ImsCallProfile createCallProfile(int callSessionType, int callType) { return super.createCallProfile(callSessionType, callType); diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4e319a12dcdeab325db1cf88337548647d06daf2 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2019 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; + +import static org.junit.Assert.assertEquals; + +import android.os.Parcel; +import android.telephony.CallQuality; +import android.telephony.CallState; +import android.telephony.PreciseCallState; +import android.telephony.TelephonyManager; +import android.telephony.ims.ImsCallProfile; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; + +/** + * Simple GTS test verifying the parceling and unparceling of CallAttributes. + */ +public class CallStateTest extends AndroidTestCase { + + + @SmallTest + public void testParcelUnparcelPreciseCallState() { + CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_INCOMING) + .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) + .setCallQuality(null) + .setCallClassification(CallState.CALL_CLASSIFICATION_RINGING) + .setImsCallSessionId("1") + .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE) + .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build(); + + Parcel parcel = Parcel.obtain(); + data.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + CallState unparceledData = CallState.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertEquals("PreciseCallState is not equal after parceled/unparceled", + data.getCallState(), + unparceledData.getCallState()); + } + + @SmallTest + public void testParcelUnparcelCallQuality() { + CallQuality quality = new CallQuality(); + CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_IDLE) + .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) + .setCallQuality(null) + .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND) + .setImsCallSessionId(null) + .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE) + .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build(); + + + Parcel parcel = Parcel.obtain(); + data.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + CallState unparceledData = CallState.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertNull(unparceledData.getCallQuality()); + + data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_IDLE) + .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) + .setCallQuality(quality) + .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND) + .setImsCallSessionId(null) + .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE) + .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build(); + + + parcel = Parcel.obtain(); + data.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + unparceledData = CallState.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertEquals("CallQuality is not equal after parceled/unparceled", + data.getCallQuality(), + unparceledData.getCallQuality()); + } + + @SmallTest + public void testParcelUnparcelNetworkTypeAndClassification() { + CallQuality quality = new CallQuality(); + CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_DIALING) + .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) + .setCallQuality(null) + .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND) + .setImsCallSessionId("3") + .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE) + .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build(); + + Parcel parcel = Parcel.obtain(); + data.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + CallState unparceledData = CallState.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertEquals("NetworkType is not equal after parceled/unparceled", + data.getNetworkType(), + unparceledData.getNetworkType()); + assertEquals("Call classification is not equal after parceled/unparceled", + data.getCallClassification(), + unparceledData.getCallClassification()); + } + + @Test + public void testParcelUnparcelImsCallInfo() { + CallQuality quality = new CallQuality(); + CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_DIALING) + .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) + .setCallQuality(null) + .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND) + .setImsCallSessionId(null) + .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NORMAL) + .setImsCallType(ImsCallProfile.CALL_TYPE_VOICE).build(); + + Parcel parcel = Parcel.obtain(); + data.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + CallState unparceledData = CallState.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertNull(unparceledData.getImsCallSessionId()); + + assertEquals("Ims call service type is not equal after parceled/unparceled", + data.getImsCallServiceType(), + unparceledData.getImsCallServiceType()); + + assertEquals("Ims call type is not equal after parceled/unparceled", + data.getImsCallType(), + unparceledData.getImsCallType()); + + data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE) + .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) + .setCallQuality(quality) + .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND) + .setImsCallSessionId("2") + .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NORMAL) + .setImsCallType(ImsCallProfile.CALL_TYPE_VT).build(); + + parcel = Parcel.obtain(); + data.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + unparceledData = CallState.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertEquals("Ims call session ID is not equal after parceled/unparceled", + data.getImsCallSessionId(), + unparceledData.getImsCallSessionId()); + + assertEquals("Ims call service type is not equal after parceled/unparceled", + data.getImsCallServiceType(), + unparceledData.getImsCallServiceType()); + + assertEquals("Ims call type is not equal after parceled/unparceled", + data.getImsCallType(), + unparceledData.getImsCallType()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eb18adb6a739a4f334664273ca778c80cc98f2b3 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.telephony; + +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_CHANGE; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_POWER_UP; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_IMS_ONLY; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_NONE; +import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_USER_CHANGE; +import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL; +import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT; +import static android.telephony.CarrierConfigManager.ImsSs.KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY; +import static android.telephony.CarrierConfigManager.ImsSs.SUPPLEMENTARY_SERVICE_CW; + +import static com.android.internal.telephony.CallWaitingController.KEY_CS_SYNC; +import static com.android.internal.telephony.CallWaitingController.KEY_STATE; +import static com.android.internal.telephony.CallWaitingController.KEY_SUB_ID; +import static com.android.internal.telephony.CallWaitingController.PREFERENCE_TBCW; +import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_ACTIVATED; +import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_NOT_ACTIVATED; +import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED; +import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA; +import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE; +import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.os.PersistableBundle; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class CallWaitingControllerTest extends TelephonyTest { + private static final int FAKE_SUB_ID = 1; + + private static final int GET_DONE = 1; + + private CallWaitingController mCWC; + private GetTestHandler mHandler; + + @Before + public void setUp() throws Exception { + super.setUp(this.getClass().getSimpleName()); + mSimulatedCommands.setRadioPower(true, null); + mPhone.mCi = this.mSimulatedCommands; + doReturn(FAKE_SUB_ID).when(mPhone).getSubId(); + + mCWC = new CallWaitingController(mPhone); + logd("CallWaitingController initiated, waiting for Power on"); + /* Make sure radio state is power on before dial. + * When radio state changed from off to on, CallTracker + * will poll result from RIL. Avoid dialing triggered at the same*/ + processAllMessages(); + } + + @After + public void tearDown() throws Exception { + mCWC = null; + super.tearDown(); + } + + @Test + @SmallTest + public void testSetTerminalBasedCallWaitingSupported() { + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + + mCWC.setTerminalBasedCallWaitingSupported(false); + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_SUPPORTED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED); + } + + @Test + @SmallTest + public void testInitialize() { + mCWC.setTerminalBasedCallWaitingSupported(false); + setPreference(mPhone.getPhoneId(), FAKE_SUB_ID, + TERMINAL_BASED_ACTIVATED, CALL_WAITING_SYNC_NONE); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true); + doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), any()); + mCWC.setTerminalBasedCallWaitingSupported(true); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + + mCWC.setTerminalBasedCallWaitingSupported(false); + setPreference(mPhone.getPhoneId(), FAKE_SUB_ID, + TERMINAL_BASED_NOT_ACTIVATED, CALL_WAITING_SYNC_NONE); + mCWC.setTerminalBasedCallWaitingSupported(true); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED); + + mCWC.setTerminalBasedCallWaitingSupported(false); + bundle = getConfigBundle(false, CALL_WAITING_SYNC_NONE, false); + doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), any()); + mCWC.setTerminalBasedCallWaitingSupported(true); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_SUPPORTED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED); + } + + @Test + @SmallTest + public void testCarrierConfigChanged() { + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + + bundle = getConfigBundle(false, CALL_WAITING_SYNC_NONE, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, false); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_SUPPORTED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED); + } + + + private static class GetTestHandler extends Handler { + public int[] resp; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case GET_DONE: + AsyncResult ar = (AsyncResult) msg.obj; + resp = (int[]) ar.result; + break; + default: + } + } + + public void reset() { + resp = null; + } + } + + @Test + @SmallTest + public void testGetCallWaitingSyncNone() { + mCWC.setTerminalBasedCallWaitingSupported(false); + assertFalse(mCWC.getCallWaiting(null)); + + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + + mHandler = new GetTestHandler(); + + assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null)); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE))); + mTestableLooper.processAllMessages(); + + assertNotNull(mHandler.resp); + assertEquals(2, mHandler.resp.length); + assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]); + assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]); + + mHandler.reset(); + + assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null)); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE))); + mTestableLooper.processAllMessages(); + + assertNotNull(mHandler.resp); + assertEquals(2, mHandler.resp.length); + assertEquals(TERMINAL_BASED_NOT_ACTIVATED, mHandler.resp[0]); + assertEquals(SERVICE_CLASS_NONE, mHandler.resp[1]); + } + + @Test + @SmallTest + public void testSetCallWaitingSyncNone() { + mCWC.setTerminalBasedCallWaitingSupported(false); + assertFalse(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null)); + + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + + assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null)); + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED); + + assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE | SERVICE_CLASS_DATA, null)); + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED); + + assertFalse(mCWC.setCallWaiting(true, SERVICE_CLASS_DATA, null)); + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED); + + assertFalse(mCWC.setCallWaiting(true, SERVICE_CLASS_NONE, null)); + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED); + } + + @Test + @SmallTest + public void testSyncUserChange() { + mCWC.setTerminalBasedCallWaitingSupported(false); + setPreference(mPhone.getPhoneId(), FAKE_SUB_ID, + TERMINAL_BASED_ACTIVATED, CALL_WAITING_SYNC_USER_CHANGE); + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_USER_CHANGE, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED); + + mHandler = new GetTestHandler(); + + mSimulatedCommands.setCallWaiting(false, SERVICE_CLASS_VOICE, null); + + assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE))); + mTestableLooper.processAllMessages(); + + assertNotNull(mHandler.resp); + assertEquals(2, mHandler.resp.length); + assertEquals(TERMINAL_BASED_NOT_ACTIVATED, mHandler.resp[0]); + assertEquals(SERVICE_CLASS_NONE, mHandler.resp[1]); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED); + + mHandler.reset(); + + mSimulatedCommands.setCallWaiting(true, SERVICE_CLASS_VOICE, null); + + assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE))); + mTestableLooper.processAllMessages(); + + assertNotNull(mHandler.resp); + assertEquals(2, mHandler.resp.length); + assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]); + assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED); + + mHandler.reset(); + + assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null)); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED); + } + + @Test + @SmallTest + public void testSyncFirstPowerUp() { + mCWC.setTerminalBasedCallWaitingSupported(false); + setPreference(mPhone.getPhoneId(), FAKE_SUB_ID, + TERMINAL_BASED_NOT_ACTIVATED, CALL_WAITING_SYNC_FIRST_POWER_UP); + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_FIRST_POWER_UP, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + assertFalse(mCWC.getSyncState()); + + mCWC.notifyRegisteredToNetwork(); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getSyncState()); + } + + @Test + @SmallTest + public void testSyncFirstChange() { + mCWC.setTerminalBasedCallWaitingSupported(false); + setPreference(mPhone.getPhoneId(), FAKE_SUB_ID, + TERMINAL_BASED_NOT_ACTIVATED, CALL_WAITING_SYNC_FIRST_CHANGE); + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_FIRST_CHANGE, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + mCWC.setImsRegistrationState(false); + + assertFalse(mCWC.getSyncState()); + + mSimulatedCommands.setCallWaiting(false, SERVICE_CLASS_VOICE, null); + mCWC.getCallWaiting(null); + mTestableLooper.processAllMessages(); + + assertFalse(mCWC.getSyncState()); + + mSimulatedCommands.setCallWaiting(true, SERVICE_CLASS_VOICE, null); + mCWC.getCallWaiting(null); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getSyncState()); + + assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null)); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mSimulatedCommands.mCallWaitActivated); + + assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null)); + mTestableLooper.processAllMessages(); + + // Local setting changed, but no change in CS network. + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mSimulatedCommands.mCallWaitActivated); + } + + @Test + @SmallTest + public void testSyncImsOnly() { + mCWC.setTerminalBasedCallWaitingSupported(false); + setPreference(mPhone.getPhoneId(), FAKE_SUB_ID, + TERMINAL_BASED_ACTIVATED, CALL_WAITING_SYNC_IMS_ONLY); + mCWC.setTerminalBasedCallWaitingSupported(true); + PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_IMS_ONLY, true); + when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle); + mCWC.updateCarrierConfig(FAKE_SUB_ID, true); + + mSimulatedCommands.setCallWaiting(false, SERVICE_CLASS_VOICE, null); + + // IMS is registered + mCWC.setImsRegistrationState(true); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED); + + mHandler = new GetTestHandler(); + + assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE))); + mTestableLooper.processAllMessages(); + + // result carries the service state from IMS service + assertNotNull(mHandler.resp); + assertEquals(2, mHandler.resp.length); + assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]); + assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED); + + mHandler.reset(); + + // IMS is not registered + mCWC.setImsRegistrationState(false); + + assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE))); + mTestableLooper.processAllMessages(); + + // result carries the service state from CS + assertNotNull(mHandler.resp); + assertEquals(2, mHandler.resp.length); + assertEquals(TERMINAL_BASED_NOT_ACTIVATED, mHandler.resp[0]); + assertEquals(SERVICE_CLASS_NONE, mHandler.resp[1]); + + // service state not synchronized between CS and IMS + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED); + + mHandler.reset(); + + // IMS is registered + mCWC.setImsRegistrationState(true); + + assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null)); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED); + + // IMS is not registered + mCWC.setImsRegistrationState(false); + + assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null)); + mTestableLooper.processAllMessages(); + + assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE))); + mTestableLooper.processAllMessages(); + + // result carries the service state from CS + assertNotNull(mHandler.resp); + assertEquals(2, mHandler.resp.length); + assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]); + assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]); + + // service state not synchronized between CS and IMS + assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED); + assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED); + assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED); + } + + private PersistableBundle getConfigBundle(boolean provisioned, + int preference, boolean defaultState) { + PersistableBundle bundle = new PersistableBundle(); + bundle.putIntArray(KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY, + provisioned ? new int[] { SUPPLEMENTARY_SERVICE_CW } : new int[] { }); + bundle.putInt(KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT, preference); + bundle.putBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL, defaultState); + return bundle; + } + + private int retrieveStatePreference(int subId) { + SharedPreferences sp = + mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE); + return sp.getInt(KEY_STATE + subId, TERMINAL_BASED_NOT_SUPPORTED); + } + + private void setPreference(int phoneId, int subId, int state, int syncPreference) { + SharedPreferences sp = + mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE); + + SharedPreferences.Editor editor = sp.edit(); + editor.putInt(KEY_SUB_ID + phoneId, subId); + editor.putInt(KEY_STATE + subId, state); + editor.putInt(KEY_CS_SYNC + phoneId, syncPreference); + editor.apply(); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..40be490587d66393ee4d5b7c651af48ecb73bf1a --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java @@ -0,0 +1,522 @@ +/* + * 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; + +import static com.android.internal.telephony.CellBroadcastConfigTracker.mergeRangesAsNeeded; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.AsyncResult; +import android.os.Message; +import android.telephony.CellBroadcastIdRange; +import android.telephony.SmsCbMessage; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; +import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public final class CellBroadcastConfigTrackerTest extends TelephonyTest { + + private CommandsInterface mSpyCi; + private CellBroadcastConfigTracker mTracker; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + mSpyCi = spy(mSimulatedCommands); + mPhone = new GsmCdmaPhone(mContext, mSpyCi, mNotifier, true, 0, + PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager); + mTracker = CellBroadcastConfigTracker.make(mPhone, mPhone, true); + mPhone.mCellBroadcastConfigTracker = mTracker; + processAllMessages(); + } + + @After + public void tearDown() throws Exception { + mPhone.removeCallbacksAndMessages(null); + mPhone = null; + super.tearDown(); + } + + @Test + public void testSetCellBroadcastIdRangesSuccess() throws Exception { + final int[][] channelValues = { + {0, 999}, {1000, 1003}, {1004, 0x0FFF}, {0x1000, 0x10FF}, {0x1100, 0x112F}, + {0x1130, 0x1900}, {0x1901, 0x9FFF}, {0xA000, 0xFFFE}, {0xFFFF, 0xFFFF}}; + List ranges = new ArrayList<>(); + for (int i = 0; i < channelValues.length; i++) { + ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1], + SmsCbMessage.MESSAGE_FORMAT_3GPP, i > 0 ? true : false)); + } + + List gsmConfigs = new ArrayList<>(); + gsmConfigs.add(new SmsBroadcastConfigInfo(0, 999, 0, 255, false)); + gsmConfigs.add(new SmsBroadcastConfigInfo(1000, 0xFFFF, 0, 255, true)); + + ArgumentCaptor gsmCaptor = ArgumentCaptor.forClass( + SmsBroadcastConfigInfo[].class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + + mockCommandInterface(); + + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r)); + processAllMessages(); + + verify(mSpyCi, times(1)).setGsmBroadcastConfig(gsmCaptor.capture(), msgCaptor.capture()); + List gsmArgs = Arrays.asList( + (SmsBroadcastConfigInfo[]) gsmCaptor.getValue()); + assertEquals(gsmConfigs, gsmArgs); + + Message msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(1)).setGsmBroadcastActivation(eq(true), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, never()).setCdmaBroadcastConfig(any(), any()); + verify(mSpyCi, never()).setCdmaBroadcastActivation(anyBoolean(), any()); + + assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges)); + + //Verify to set cdma config and activate, but no more for gsm as no change + for (int i = 0; i < channelValues.length; i++) { + ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1], + SmsCbMessage.MESSAGE_FORMAT_3GPP2, i > 0 ? true : false)); + } + List cdmaConfigs = new ArrayList<>(); + cdmaConfigs.add(new CdmaSmsBroadcastConfigInfo(0, 999, 1, false)); + cdmaConfigs.add(new CdmaSmsBroadcastConfigInfo(1000, 0xFFFF, 1, true)); + ArgumentCaptor cdmaCaptor = ArgumentCaptor.forClass( + CdmaSmsBroadcastConfigInfo[].class); + + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r)); + processAllMessages(); + + verify(mSpyCi, times(1)).setGsmBroadcastConfig(any(), any()); + verify(mSpyCi, times(1)).setCdmaBroadcastConfig(cdmaCaptor.capture(), msgCaptor.capture()); + List cdmaArgs = Arrays.asList( + (CdmaSmsBroadcastConfigInfo[]) cdmaCaptor.getValue()); + assertEquals(cdmaConfigs, cdmaArgs); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(1)).setGsmBroadcastActivation(anyBoolean(), any()); + verify(mSpyCi, times(1)).setCdmaBroadcastActivation(eq(true), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges)); + + // Verify not to set cdma or gsm config as the config is not changed + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r)); + processAllMessages(); + + verify(mSpyCi, times(1)).setCdmaBroadcastConfig(any(), any()); + verify(mSpyCi, times(1)).setCdmaBroadcastActivation(anyBoolean(), any()); + verify(mSpyCi, times(1)).setGsmBroadcastConfig(any(), any()); + verify(mSpyCi, times(1)).setGsmBroadcastActivation(anyBoolean(), any()); + + assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges)); + + // Verify to reset ranges with empty ranges list + mPhone.setCellBroadcastIdRanges(new ArrayList<>(), r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r)); + + processAllMessages(); + + verify(mSpyCi, times(2)).setGsmBroadcastConfig(gsmCaptor.capture(), msgCaptor.capture()); + assertEquals(0, ((SmsBroadcastConfigInfo[]) gsmCaptor.getValue()).length); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + // Verify to deavtivate gsm broadcast on empty ranges + verify(mSpyCi, times(1)).setGsmBroadcastActivation(eq(false), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(2)).setCdmaBroadcastConfig(cdmaCaptor.capture(), msgCaptor.capture()); + assertEquals(0, ((CdmaSmsBroadcastConfigInfo[]) cdmaCaptor.getValue()).length); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + // Verify to deavtivate cdma broadcast on empty ranges + verify(mSpyCi, times(1)).setCdmaBroadcastActivation(eq(false), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty()); + + //Verify to set gsm and cdma config then activate again + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r)); + + processAllMessages(); + + verify(mSpyCi, times(3)).setGsmBroadcastConfig(gsmCaptor.capture(), msgCaptor.capture()); + gsmArgs = Arrays.asList((SmsBroadcastConfigInfo[]) gsmCaptor.getValue()); + assertEquals(gsmConfigs, gsmArgs); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(2)).setGsmBroadcastActivation(eq(true), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(3)).setCdmaBroadcastConfig(cdmaCaptor.capture(), msgCaptor.capture()); + cdmaArgs = Arrays.asList((CdmaSmsBroadcastConfigInfo[]) cdmaCaptor.getValue()); + assertEquals(cdmaConfigs, cdmaArgs); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(2)).setCdmaBroadcastActivation(eq(true), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges)); + } + + @Test + public void testSetCellBroadcastIdRangesFailure() throws Exception { + List ranges = new ArrayList<>(); + + // Verify to throw exception for invalid ranges + ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true)); + ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, false)); + + assertThrows(IllegalArgumentException.class, + () -> mPhone.setCellBroadcastIdRanges(ranges, r -> {})); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + ranges.clear(); + ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true)); + ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP2, true)); + + mockCommandInterface(); + + // Verify the result on setGsmBroadcastConfig failure + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG == r)); + processAllMessages(); + + verify(mSpyCi, times(1)).setGsmBroadcastConfig(any(), msgCaptor.capture()); + + Message msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg).exception = new RuntimeException(); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(0)).setGsmBroadcastActivation(anyBoolean(), any()); + verify(mSpyCi, times(0)).setCdmaBroadcastConfig(any(), any()); + verify(mSpyCi, times(0)).setCdmaBroadcastActivation(anyBoolean(), any()); + assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty()); + + // Verify the result on setGsmBroadcastActivation failure + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION == r)); + processAllMessages(); + + verify(mSpyCi, times(2)).setGsmBroadcastConfig(any(), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(1)).setGsmBroadcastActivation(anyBoolean(), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg).exception = new RuntimeException(); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(0)).setCdmaBroadcastConfig(any(), any()); + verify(mSpyCi, times(0)).setCdmaBroadcastActivation(anyBoolean(), any()); + assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty()); + + // Verify the result on setCdmaBroadcastConfig failure + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG == r)); + processAllMessages(); + + verify(mSpyCi, times(3)).setGsmBroadcastConfig(any(), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(2)).setGsmBroadcastActivation(anyBoolean(), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(1)).setCdmaBroadcastConfig(any(), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg).exception = new RuntimeException(); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(0)).setCdmaBroadcastActivation(anyBoolean(), any()); + + List ranges3gpp = new ArrayList<>(); + ranges3gpp.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true)); + + assertEquals(mPhone.getCellBroadcastIdRanges(), ranges3gpp); + + // Verify the result on setCdmaBroadcastActivation failure + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION == r)); + processAllMessages(); + + // Verify no more calls as there is no change of ranges for 3gpp + verify(mSpyCi, times(3)).setGsmBroadcastConfig(any(), any()); + verify(mSpyCi, times(2)).setGsmBroadcastActivation(anyBoolean(), any()); + verify(mSpyCi, times(2)).setCdmaBroadcastConfig(any(), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg); + msg.sendToTarget(); + processAllMessages(); + + verify(mSpyCi, times(1)).setCdmaBroadcastActivation(anyBoolean(), msgCaptor.capture()); + + msg = msgCaptor.getValue(); + assertNotNull(msg); + AsyncResult.forMessage(msg).exception = new RuntimeException(); + msg.sendToTarget(); + processAllMessages(); + + assertEquals(mPhone.getCellBroadcastIdRanges(), ranges3gpp); + } + + @Test + public void testClearCellBroadcastConfigOnRadioOff() { + List ranges = new ArrayList<>(); + ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true)); + + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r)); + processAllMessages(); + + assertEquals(mPhone.getCellBroadcastIdRanges(), ranges); + + mPhone.sendEmptyMessage(Phone.EVENT_RADIO_OFF_OR_NOT_AVAILABLE); + processAllMessages(); + + // Verify the config is reset + assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty()); + } + + @Test + public void testClearCellBroadcastConfigOnSubscriptionChanged() { + List ranges = new ArrayList<>(); + ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true)); + + mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue( + TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r)); + processAllMessages(); + + assertEquals(mPhone.getCellBroadcastIdRanges(), ranges); + + mTracker.mSubChangedListener.onSubscriptionsChanged(); + processAllMessages(); + + // Verify the config is not reset when the sub id is not changed + assertEquals(mPhone.getCellBroadcastIdRanges(), ranges); + + mTracker.mSubId = mTracker.mSubId % SubscriptionManager.DEFAULT_SUBSCRIPTION_ID + 1; + + mTracker.mSubChangedListener.onSubscriptionsChanged(); + processAllMessages(); + + // Verify the config is reset when the sub id is changed + assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty()); + } + + @Test + public void testMergeCellBroadcastIdRangesAsNeeded() { + final int[][] channelValues = { + {0, 999}, {1000, 1003}, {1004, 0x0FFF}, {0x1000, 0x10FF}, {0x1100, 0x112F}, + {0x1130, 0x1900}, {0x1901, 0x9FFF}, {0xA000, 0xFFFE}, {0xFFFF, 0xFFFF}}; + final int[] typeValues = { + SmsCbMessage.MESSAGE_FORMAT_3GPP, SmsCbMessage.MESSAGE_FORMAT_3GPP2}; + final boolean[] enabledValues = {true, false}; + + List ranges = new ArrayList<>(); + for (int i = 0; i < channelValues.length; i++) { + ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1], + typeValues[0], enabledValues[0])); + } + + ranges = mergeRangesAsNeeded(ranges); + + assertEquals(1, ranges.size()); + assertEquals(ranges.get(0).getStartId(), channelValues[0][0]); + assertEquals(ranges.get(0).getEndId(), channelValues[channelValues.length - 1][0]); + + // Verify not to merge the ranges with different types. + ranges.clear(); + for (int i = 0; i < channelValues.length; i++) { + ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1], + typeValues[0], enabledValues[0])); + ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1], + typeValues[1], enabledValues[0])); + } + + ranges = mergeRangesAsNeeded(ranges); + + assertEquals(2, ranges.size()); + assertEquals(ranges.get(0).getStartId(), channelValues[0][0]); + assertEquals(ranges.get(0).getEndId(), channelValues[channelValues.length - 1][0]); + assertEquals(ranges.get(1).getStartId(), channelValues[0][0]); + assertEquals(ranges.get(1).getEndId(), channelValues[channelValues.length - 1][0]); + assertTrue(ranges.get(0).getType() != ranges.get(1).getType()); + + // Verify to throw IllegalArgumentException if the same range is enabled and disabled + // in the range list. + final List ranges2 = new ArrayList<>(); + for (int i = 0; i < channelValues.length; i++) { + ranges2.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1], + typeValues[0], enabledValues[0])); + ranges2.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1], + typeValues[0], enabledValues[1])); + } + + assertThrows(IllegalArgumentException.class, () -> + mergeRangesAsNeeded(ranges2)); + } + + @Test + public void testMakeCellBroadcastConfigTracker() { + Phone phone = spy(mPhone); + CellBroadcastConfigTracker tracker = CellBroadcastConfigTracker.make(phone, phone, false); + processAllMessages(); + + verify(phone, never()).registerForRadioOffOrNotAvailable(any(), anyInt(), any()); + verify(mSubscriptionManager, never()).addOnSubscriptionsChangedListener( + any(), eq(tracker.mSubChangedListener)); + + tracker.start(); + processAllMessages(); + + verify(phone, times(1)).registerForRadioOffOrNotAvailable(any(), anyInt(), any()); + verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener( + any(), eq(tracker.mSubChangedListener)); + + tracker = CellBroadcastConfigTracker.make(phone, phone, true); + processAllMessages(); + + verify(phone, times(2)).registerForRadioOffOrNotAvailable(any(), anyInt(), any()); + verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener( + any(), eq(tracker.mSubChangedListener)); + } + + private void mockCommandInterface() { + doNothing().when(mSpyCi).setGsmBroadcastConfig(any(), any()); + doNothing().when(mSpyCi).setGsmBroadcastActivation(anyBoolean(), any()); + doNothing().when(mSpyCi).setCdmaBroadcastConfig(any(), any()); + doNothing().when(mSpyCi).setCdmaBroadcastActivation(anyBoolean(), any()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java index 47f545f995437aedea205a4bb433f5d244086930..11d57bfaa5a88246481c114d46b05e6970a23b1b 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import android.hardware.radio.V1_6.NrSignalStrength; +import android.hardware.radio.network.NrSignalStrength; import android.os.Parcel; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; @@ -55,9 +55,11 @@ public class CellSignalStrengthNrTest extends TelephonyTest { private static final int CSICQI_TABLE_INDEX = 1; private static final ArrayList CSICQI_REPORT = new ArrayList<>(Arrays.asList((byte) 3, (byte) 2, (byte) 1)); + private static final byte[] CSICQI_REPORT_PRIMITIVE = new byte[] {(byte) 3, (byte) 2, (byte) 1}; private static final int SSRSRP = -112; private static final int SSRSRQ = -13; private static final int SSSINR = 32; + private static final int TIMING_ADVANCE = 10; // Mocked classes ServiceState mSS; @@ -83,7 +85,7 @@ public class CellSignalStrengthNrTest extends TelephonyTest { public void testGetMethod() { // GIVEN an instance of CellSignalStrengthNr CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); // THEN the get method should return correct value assertThat(css.getCsiRsrp()).isEqualTo(CSIRSRP); @@ -95,20 +97,22 @@ public class CellSignalStrengthNrTest extends TelephonyTest { assertThat(css.getSsRsrq()).isEqualTo(SSRSRQ); assertThat(css.getSsSinr()).isEqualTo(SSSINR); assertThat(css.getDbm()).isEqualTo(SSRSRP); + assertThat(css.getTimingAdvanceMicros()).isEqualTo(TIMING_ADVANCE); } @Test public void testGetMethodWithHal() { // GIVEN an instance of NrSignalStrength with some positive values NrSignalStrength nrSignalStrength = new NrSignalStrength(); - nrSignalStrength.base.csiRsrp = -CSIRSRP; - nrSignalStrength.base.csiRsrq = -CSIRSRQ; - nrSignalStrength.base.csiSinr = CSISINR; + nrSignalStrength.csiRsrp = -CSIRSRP; + nrSignalStrength.csiRsrq = -CSIRSRQ; + nrSignalStrength.csiSinr = CSISINR; nrSignalStrength.csiCqiTableIndex = CSICQI_TABLE_INDEX; - nrSignalStrength.csiCqiReport = CSICQI_REPORT; - nrSignalStrength.base.ssRsrp = -SSRSRP; - nrSignalStrength.base.ssRsrq = -SSRSRQ; - nrSignalStrength.base.ssSinr = SSSINR; + nrSignalStrength.csiCqiReport = CSICQI_REPORT_PRIMITIVE; + nrSignalStrength.ssRsrp = -SSRSRP; + nrSignalStrength.ssRsrq = -SSRSRQ; + nrSignalStrength.ssSinr = SSSINR; + nrSignalStrength.timingAdvance = TIMING_ADVANCE; // THEN the get method should return the correct value CellSignalStrengthNr css = RILUtils.convertHalNrSignalStrength(nrSignalStrength); @@ -121,20 +125,22 @@ public class CellSignalStrengthNrTest extends TelephonyTest { assertThat(css.getSsRsrq()).isEqualTo(SSRSRQ); assertThat(css.getSsSinr()).isEqualTo(SSSINR); assertThat(css.getDbm()).isEqualTo(SSRSRP); + assertThat(css.getTimingAdvanceMicros()).isEqualTo(TIMING_ADVANCE); } @Test public void testUnavailableValueWithHal() { // GIVEN an instance of NrSignalStrength NrSignalStrength nrSignalStrength = new NrSignalStrength(); - nrSignalStrength.base.csiRsrp = CellInfo.UNAVAILABLE; - nrSignalStrength.base.csiRsrq = CellInfo.UNAVAILABLE; - nrSignalStrength.base.csiSinr = CellInfo.UNAVAILABLE; + nrSignalStrength.csiRsrp = CellInfo.UNAVAILABLE; + nrSignalStrength.csiRsrq = CellInfo.UNAVAILABLE; + nrSignalStrength.csiSinr = CellInfo.UNAVAILABLE; nrSignalStrength.csiCqiTableIndex = CellInfo.UNAVAILABLE; - nrSignalStrength.csiCqiReport = new ArrayList(); - nrSignalStrength.base.ssRsrp = CellInfo.UNAVAILABLE; - nrSignalStrength.base.ssRsrq = CellInfo.UNAVAILABLE; - nrSignalStrength.base.ssSinr = CellInfo.UNAVAILABLE; + nrSignalStrength.csiCqiReport = new byte[]{}; + nrSignalStrength.ssRsrp = CellInfo.UNAVAILABLE; + nrSignalStrength.ssRsrq = CellInfo.UNAVAILABLE; + nrSignalStrength.ssSinr = CellInfo.UNAVAILABLE; + nrSignalStrength.timingAdvance = CellInfo.UNAVAILABLE; // THEN the get method should return unavailable value CellSignalStrengthNr css = RILUtils.convertHalNrSignalStrength(nrSignalStrength); @@ -147,15 +153,16 @@ public class CellSignalStrengthNrTest extends TelephonyTest { assertThat(css.getSsRsrq()).isEqualTo(CellInfo.UNAVAILABLE); assertThat(css.getSsSinr()).isEqualTo(CellInfo.UNAVAILABLE); assertThat(css.getDbm()).isEqualTo(CellInfo.UNAVAILABLE); + assertThat(css.getTimingAdvanceMicros()).isEqualTo(CellInfo.UNAVAILABLE); } @Test public void testEquals_sameParameters() { // GIVEN an instance of CellSignalStrengthNr and another object with the same parameters CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); CellSignalStrengthNr anotherCss = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); // THEN this two objects are equivalent assertThat(css).isEqualTo(anotherCss); @@ -166,10 +173,10 @@ public class CellSignalStrengthNrTest extends TelephonyTest { // GIVEN an instance of CellSignalStrengthNr and another object with some different // parameters CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); CellSignalStrengthNr anotherCss = new CellSignalStrengthNr(ANOTHER_CSIRSRP, ANOTHER_CSIRSRQ, CSISINR, CSICQI_TABLE_INDEX, CSICQI_REPORT, - SSRSRP, SSRSRQ, SSSINR); + SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); // THEN this two objects are different assertThat(css).isNotEqualTo(anotherCss); @@ -179,7 +186,7 @@ public class CellSignalStrengthNrTest extends TelephonyTest { public void testAusLevel_validValue() { // GIVEN an instance of CellSignalStrengthNr with valid csirsrp CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); // THEN the asu level is in range [0, 97] assertThat(css.getAsuLevel()).isIn(Range.range(0, BoundType.CLOSED, 97, BoundType.CLOSED)); @@ -189,7 +196,7 @@ public class CellSignalStrengthNrTest extends TelephonyTest { public void testAsuLevel_invalidValue() { // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp CellSignalStrengthNr css = new CellSignalStrengthNr(INVALID_CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, INVALID_SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, INVALID_SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); // THEN the asu level is unknown assertThat(css.getAsuLevel()).isEqualTo(CellSignalStrengthNr.UNKNOWN_ASU_LEVEL); @@ -200,7 +207,7 @@ public class CellSignalStrengthNrTest extends TelephonyTest { for (int ssRsrp = -156; ssRsrp <= -31; ssRsrp++) { // GIVEN an instance of CellSignalStrengthNr with valid csirsrp CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, ssRsrp, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, ssRsrp, SSRSRQ, SSSINR, TIMING_ADVANCE); // THEN the signal level is valid assertThat(css.getLevel()).isIn(Range.range( @@ -213,7 +220,7 @@ public class CellSignalStrengthNrTest extends TelephonyTest { public void testSignalLevel_invalidValue() { // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp CellSignalStrengthNr css = new CellSignalStrengthNr(INVALID_CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); // THEN the signal level is unknown assertThat(css.getLevel()).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); @@ -223,7 +230,7 @@ public class CellSignalStrengthNrTest extends TelephonyTest { public void testParcel() { // GIVEN an instance of CellSignalStrengthNr CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR, - CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR); + CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE); // WHEN write the object to parcel and create another object with that parcel Parcel parcel = Parcel.obtain(); @@ -241,6 +248,7 @@ public class CellSignalStrengthNrTest extends TelephonyTest { assertThat(anotherCss.getSsRsrp()).isEqualTo(SSRSRP); assertThat(anotherCss.getSsRsrq()).isEqualTo(SSRSRQ); assertThat(anotherCss.getSsSinr()).isEqualTo(SSSINR); + assertThat(anotherCss.getTimingAdvanceMicros()).isEqualTo(TIMING_ADVANCE); } @Test diff --git a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java old mode 100755 new mode 100644 index d1e643e2b5ffa16a179dcc695ea5ed0b6d31be08..0bce5cbd0762917e568785a1eebcd7e3e48f821b --- a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java @@ -20,12 +20,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.os.Handler; import android.os.Looper; +import com.android.internal.telephony.emergency.EmergencyNumberTracker; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -39,6 +43,9 @@ public class ConnectionTest extends TelephonyTest { // Mocked classes protected Call mCall; + protected EmergencyNumberTracker mEmergencyNumberTracker2; + protected Connection.PhoneFactoryProxy mPhoneFactoryProxy; + protected Connection mTestConnection; private class TestConnection extends Connection { @@ -117,7 +124,15 @@ public class ConnectionTest extends TelephonyTest { super.setUp(getClass().getSimpleName()); mCall = mock(Call.class); doReturn(mPhone).when(mCall).getPhone(); + doReturn(mPhone).when(mCT).getPhone(); replaceInstance(Handler.class, "mLooper", mCT, Looper.getMainLooper()); + + mEmergencyNumberTracker2 = mock(EmergencyNumberTracker.class); + doReturn(mEmergencyNumberTracker2).when(mPhone2).getEmergencyNumberTracker(); + + mTestConnection = new TestConnection(TEST_PHONE_TYPE); + mPhoneFactoryProxy = mock(Connection.PhoneFactoryProxy.class); + mTestConnection.setPhoneFactoryProxy(mPhoneFactoryProxy); } @After @@ -156,5 +171,24 @@ public class ConnectionTest extends TelephonyTest { assertTrue(connection.hasKnownUserIntentEmergency()); } + @Test + public void testSetEmergencyCallInfo() { + //Replicate Dual-SIM: + Phone [] phones = {mPhone, mPhone2}; + when(mPhoneFactoryProxy.getPhones()).thenReturn(phones); + doReturn(1).when(mPhone).getPhoneId(); + doReturn(2).when(mPhone2).getPhoneId(); + + //Replicate behavior when a number is an emergency number + // on the secondary SIM but not on the default SIM: + when(mPhone.getEmergencyNumberTracker().getEmergencyNumber(any())).thenReturn(null); + when(mEmergencyNumberTracker2.getEmergencyNumber(any())) + .thenReturn(getTestEmergencyNumber()); + + //Ensure the connection is considered as an emergency call: + mTestConnection.setEmergencyCallInfo(mCT); + assertTrue(mTestConnection.isEmergencyCall()); + } + // TODO Verify more methods in Connection } diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java index 82ade7388e284da7d8ec6a80f2237d951b18e2c0..ea19b6262540d6932e17d869fc9ec8ec3309a018 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java @@ -176,6 +176,11 @@ public class ContextFixture implements TestFixture { mKeyValuePairs.put(request, (String)args.get("value")); mNumKeyValuePairs++; break; + case Settings.CALL_METHOD_PUT_CONFIG: + logd("PUT_config called"); + logd("adding config flag: " + request + "-" + args.getString("value")); + mFlags.put(request, args.getString("value")); + break; case Settings.CALL_METHOD_LIST_CONFIG: logd("LIST_config: " + mFlags); Bundle result = new Bundle(); @@ -351,6 +356,8 @@ public class ContextFixture implements TestFixture { return Context.POWER_SERVICE; } else if (serviceClass == EuiccManager.class) { return Context.EUICC_SERVICE; + } else if (serviceClass == AlarmManager.class) { + return Context.ALARM_SERVICE; } return super.getSystemServiceName(serviceClass); } @@ -773,6 +780,7 @@ public class ContextFixture implements TestFixture { doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt()); doReturn(mBundle).when(mCarrierConfigManager).getConfig(); + doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString()); doAnswer(invocation -> mNetworkId++).when(mNetwork).getNetId(); doReturn(mNetwork).when(mConnectivityManager).registerNetworkAgent( any(), any(), any(), any(), any(), any(), anyInt()); diff --git a/tests/telephonytests/src/com/android/internal/telephony/DataSpecificRegistrationInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/DataSpecificRegistrationInfoTest.java new file mode 100644 index 0000000000000000000000000000000000000000..50390ccd9cec5befe591b1e68483f929fc9ef9ce --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/DataSpecificRegistrationInfoTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; + +import android.os.Parcel; +import android.telephony.DataSpecificRegistrationInfo; +import android.telephony.LteVopsSupportInfo; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** Unit tests for {@link DataSpecificRegistrationInfo}. */ +public class DataSpecificRegistrationInfoTest { + @Test + @SmallTest + public void testBuilder() { + DataSpecificRegistrationInfo dsri = + new DataSpecificRegistrationInfo.Builder(3) + .setNrAvailable(true) + .setEnDcAvailable(true) + .build(); + + assertEquals(3, dsri.maxDataCalls); + assertEquals(false, dsri.isDcNrRestricted); + assertEquals(true, dsri.isNrAvailable); + assertEquals(true, dsri.isEnDcAvailable); + assertNull(dsri.getVopsSupportInfo()); + + LteVopsSupportInfo vopsInfo = new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, LteVopsSupportInfo.LTE_STATUS_SUPPORTED); + dsri = new DataSpecificRegistrationInfo.Builder(5) + .setDcNrRestricted(true) + .setVopsSupportInfo(vopsInfo) + .build(); + + assertEquals(5, dsri.maxDataCalls); + assertEquals(true, dsri.isDcNrRestricted); + assertEquals(false, dsri.isNrAvailable); + assertEquals(false, dsri.isEnDcAvailable); + assertEquals(vopsInfo, dsri.getVopsSupportInfo()); + } + + @Test + @SmallTest + public void testParcel() { + DataSpecificRegistrationInfo dsri = + new DataSpecificRegistrationInfo.Builder(3) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setLteAttachResultType(DataSpecificRegistrationInfo.LTE_ATTACH_TYPE_COMBINED) + .setLteAttachExtraInfo(DataSpecificRegistrationInfo.LTE_ATTACH_EXTRA_INFO_SMS_ONLY) + .build(); + + Parcel p = Parcel.obtain(); + dsri.writeToParcel(p, 0); + p.setDataPosition(0); + + DataSpecificRegistrationInfo newDsri = + DataSpecificRegistrationInfo.CREATOR.createFromParcel(p); + assertEquals(dsri, newDsri); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java index 6a05d4f4b85675bbac49097a61efef8deacf8125..2f4182a99345732c2155f6e543933bd609990b33 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java @@ -16,6 +16,7 @@ package com.android.internal.telephony; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; @@ -30,8 +31,11 @@ import android.telephony.PreciseCallState; import android.telephony.PreciseDisconnectCause; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; +import android.telephony.ims.ImsCallProfile; import android.test.suitebuilder.annotation.SmallTest; +import com.android.internal.telephony.imsphone.ImsPhoneCall; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -53,6 +57,9 @@ public class DefaultPhoneNotifierTest extends TelephonyTest { GsmCdmaCall mForeGroundCall; GsmCdmaCall mBackGroundCall; GsmCdmaCall mRingingCall; + ImsPhoneCall mImsForeGroundCall; + ImsPhoneCall mImsBackGroundCall; + ImsPhoneCall mImsRingingCall; @Before public void setUp() throws Exception { @@ -62,6 +69,9 @@ public class DefaultPhoneNotifierTest extends TelephonyTest { mForeGroundCall = mock(GsmCdmaCall.class); mBackGroundCall = mock(GsmCdmaCall.class); mRingingCall = mock(GsmCdmaCall.class); + mImsForeGroundCall = mock(ImsPhoneCall.class); + mImsBackGroundCall = mock(ImsPhoneCall.class); + mImsRingingCall = mock(ImsPhoneCall.class); mDefaultPhoneNotifierUT = new DefaultPhoneNotifier(mContext); } @@ -168,61 +178,144 @@ public class DefaultPhoneNotifierTest extends TelephonyTest { @Test @SmallTest public void testNotifyPreciseCallState() throws Exception { - //mock forground/background/ringing call and call state doReturn(Call.State.IDLE).when(mForeGroundCall).getState(); doReturn(Call.State.IDLE).when(mBackGroundCall).getState(); doReturn(Call.State.IDLE).when(mRingingCall).getState(); - mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone); + mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null); verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState( - anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); + anyInt(), anyInt(), any(), any(), any(), any()); doReturn(mForeGroundCall).when(mPhone).getForegroundCall(); - mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone); + mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null); verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState( - anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); + anyInt(), anyInt(), any(), any(), any(), any()); doReturn(mBackGroundCall).when(mPhone).getBackgroundCall(); - mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone); + mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null); verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState( - anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); + anyInt(), anyInt(), any(), any(), any(), any()); doReturn(mRingingCall).when(mPhone).getRingingCall(); - mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone); - verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState( - mPhone.getPhoneId(), - mPhone.getSubId(), - PreciseCallState.PRECISE_CALL_STATE_IDLE, - PreciseCallState.PRECISE_CALL_STATE_IDLE, - PreciseCallState.PRECISE_CALL_STATE_IDLE); + mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null); + ArgumentCaptor captor = ArgumentCaptor.forClass(int[].class); + int phoneId = mPhone.getPhoneId(); + int subId = mPhone.getSubId(); + verify(mTelephonyRegistryManager).notifyPreciseCallState( + eq(phoneId), eq(subId), captor.capture(), eq(null), eq(null), eq(null)); + final int[] callStates = captor.getValue(); + assertEquals(3, callStates.length); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE, + callStates[/*ringing call*/ 0]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE, + callStates[/*foreground call*/ 1]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE, + callStates[/*background call*/ 2]); doReturn(Call.State.ACTIVE).when(mForeGroundCall).getState(); - mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone); - verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState( - mPhone.getPhoneId(), - mPhone.getSubId(), - PreciseCallState.PRECISE_CALL_STATE_IDLE, - PreciseCallState.PRECISE_CALL_STATE_ACTIVE, - PreciseCallState.PRECISE_CALL_STATE_IDLE); + mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null); + ArgumentCaptor captor1 = ArgumentCaptor.forClass(int[].class); + phoneId = mPhone.getPhoneId(); + subId = mPhone.getSubId(); + verify(mTelephonyRegistryManager, times(2)).notifyPreciseCallState( + eq(phoneId), eq(subId), captor1.capture(), eq(null), eq(null), eq(null)); + final int[] callStates1 = captor1.getValue(); + assertEquals(3, callStates1.length); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE, + callStates1[/*ringing call*/ 0]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE, + callStates1[/*foreground call*/ 1]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE, + callStates1[/*background call*/ 2]); doReturn(Call.State.HOLDING).when(mBackGroundCall).getState(); - mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone); - verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState( - mPhone.getPhoneId(), - mPhone.getSubId(), - PreciseCallState.PRECISE_CALL_STATE_IDLE, - PreciseCallState.PRECISE_CALL_STATE_ACTIVE, - PreciseCallState.PRECISE_CALL_STATE_HOLDING); + mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null); + ArgumentCaptor captor2 = ArgumentCaptor.forClass(int[].class); + verify(mTelephonyRegistryManager, times(3)).notifyPreciseCallState( + eq(phoneId), eq(subId), captor2.capture(), eq(null), eq(null), eq(null)); + final int[] callStates2 = captor2.getValue(); + assertEquals(3, callStates2.length); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE, + callStates2[/*ringing call*/ 0]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE, + callStates2[/*foreground call*/ 1]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_HOLDING, + callStates2[/*background call*/ 2]); doReturn(Call.State.ALERTING).when(mRingingCall).getState(); - mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone); + mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null); + ArgumentCaptor captor3 = ArgumentCaptor.forClass(int[].class); + verify(mTelephonyRegistryManager, times(4)).notifyPreciseCallState( + eq(phoneId), eq(subId), captor3.capture(), eq(null), eq(null), eq(null)); + final int[] callStates3 = captor3.getValue(); + assertEquals(3, callStates3.length); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_ALERTING, + callStates3[/*ringing call*/ 0]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE, + callStates3[/*foreground call*/ 1]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_HOLDING, + callStates3[/*background call*/ 2]); + } + + @Test + public void testNotifyPreciseCallStateImsCallInfo() throws Exception { + //mock forground/background/ringing call and call state + doReturn(Call.State.ACTIVE).when(mImsForeGroundCall).getState(); + doReturn(Call.State.HOLDING).when(mImsBackGroundCall).getState(); + doReturn(Call.State.IDLE).when(mImsRingingCall).getState(); + + doReturn(mImsForeGroundCall).when(mImsPhone).getForegroundCall(); + doReturn(mImsBackGroundCall).when(mImsPhone).getBackgroundCall(); + doReturn(mImsRingingCall).when(mImsPhone).getRingingCall(); + + String[] imsCallIds = {null, "1", "2"}; + int[] imsCallServiceTypes = {ImsCallProfile.SERVICE_TYPE_NONE, + ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.SERVICE_TYPE_NORMAL}; + int[] imsCallTypes = {ImsCallProfile.CALL_TYPE_NONE, + ImsCallProfile.CALL_TYPE_VOICE, ImsCallProfile.CALL_TYPE_VT}; + + mDefaultPhoneNotifierUT + .notifyPreciseCallState(mImsPhone, imsCallIds, imsCallServiceTypes, imsCallTypes); + ArgumentCaptor callStateCaptor = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor callIdCaptor = ArgumentCaptor.forClass(String[].class); + ArgumentCaptor callServiceTypeCaptor = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor callTypeCaptor = ArgumentCaptor.forClass(int[].class); + int phoneId = mImsPhone.getPhoneId(); + int subId = mImsPhone.getSubId(); verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState( - mPhone.getPhoneId(), - mPhone.getSubId(), - PreciseCallState.PRECISE_CALL_STATE_ALERTING, - PreciseCallState.PRECISE_CALL_STATE_ACTIVE, - PreciseCallState.PRECISE_CALL_STATE_HOLDING); + eq(phoneId), eq(subId), callStateCaptor.capture(), callIdCaptor.capture(), + callServiceTypeCaptor.capture(), callTypeCaptor.capture()); + final int[] callStates = callStateCaptor.getValue(); + final String[] callIds = callIdCaptor.getValue(); + final int[] callServiceTypes = callServiceTypeCaptor.getValue(); + final int[] callTypes = callTypeCaptor.getValue(); + assertEquals(3, callStates.length); + assertEquals(3, callIds.length); + assertEquals(3, callServiceTypes.length); + assertEquals(3, callServiceTypes.length); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE, + callStates[/*ringing call*/ 0]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE, + callStates[/*foreground call*/ 1]); + assertEquals(PreciseCallState.PRECISE_CALL_STATE_HOLDING, + callStates[/*background call*/ 2]); + + assertEquals("1", callIds[/*foreground call*/ 1]); + assertEquals("2", callIds[/*background call*/ 2]); + assertEquals(null, callIds[/*ringing call*/ 0]); + assertEquals(ImsCallProfile.SERVICE_TYPE_NORMAL, + callServiceTypes[/*foreground call*/ 1]); + assertEquals(ImsCallProfile.SERVICE_TYPE_NORMAL, + callServiceTypes[/*background call*/ 2]); + assertEquals(ImsCallProfile.SERVICE_TYPE_NONE, + callServiceTypes[/*ringing call*/ 0]); + assertEquals(ImsCallProfile.CALL_TYPE_VOICE, + callTypes[/*foreground call*/ 1]); + assertEquals(ImsCallProfile.CALL_TYPE_VT, + callTypes[/*background call*/ 2]); + assertEquals(ImsCallProfile.SERVICE_TYPE_NONE, + callServiceTypes[/*ringing call*/ 0]); } @Test @SmallTest diff --git a/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java index d2d8d969a2f485fb508bfea1f74150b300fabc7c..f729b800cdb8780eb739fd3fcb2071d8e6bbed32 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java @@ -102,7 +102,6 @@ public class DisplayInfoControllerTest extends TelephonyTest { super.setUp(getClass().getSimpleName()); doReturn((Executor) Runnable::run).when(mContext).getMainExecutor(); - mBundle = mContextFixture.getCarrierConfigBundle(); mSstHandler = new ServiceStateTrackerTestHandler(getClass().getSimpleName()); mSstHandler.start(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java index f0c43be67c02c4354918f72c7efebc6fef649095..2fdff9ed7bddd59c897b8596a6d0dade41ad47f9 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java @@ -41,9 +41,7 @@ import android.testing.TestableLooper; import androidx.test.filters.FlakyTest; import com.android.internal.telephony.PhoneInternalInterface.DialArgs; - -import java.io.PrintWriter; -import java.io.StringWriter; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; import org.junit.After; import org.junit.Assert; @@ -53,6 +51,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.io.PrintWriter; +import java.io.StringWriter; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class GsmCdmaCallTrackerTest extends TelephonyTest { @@ -66,12 +67,16 @@ public class GsmCdmaCallTrackerTest extends TelephonyTest { // Mocked classes private GsmCdmaConnection mConnection; private Handler mHandler; + private DomainSelectionResolver mDomainSelectionResolver; @Before public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); mConnection = mock(GsmCdmaConnection.class); mHandler = mock(Handler.class); + mDomainSelectionResolver = mock(DomainSelectionResolver.class); + doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported(); + DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver); mSimulatedCommands.setRadioPower(true, null); mPhone.mCi = this.mSimulatedCommands; @@ -86,6 +91,7 @@ public class GsmCdmaCallTrackerTest extends TelephonyTest { @After public void tearDown() throws Exception { mCTUT = null; + DomainSelectionResolver.setDomainSelectionResolver(null); super.tearDown(); } diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java index b62d5f9798e35ca23cd4a0a2fb195a0032f142c8..c5f20e39a47968758341ae1d9b9ab8393faf55bf 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java @@ -19,9 +19,14 @@ package com.android.internal.telephony; import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE; import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL; import static com.android.internal.telephony.Phone.EVENT_ICC_CHANGED; +import static com.android.internal.telephony.Phone.EVENT_IMS_DEREGISTRATION_TRIGGERED; +import static com.android.internal.telephony.Phone.EVENT_RADIO_AVAILABLE; +import static com.android.internal.telephony.Phone.EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE; import static com.android.internal.telephony.Phone.EVENT_SRVCC_STATE_CHANGED; import static com.android.internal.telephony.Phone.EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED; import static com.android.internal.telephony.TelephonyTestUtils.waitForMs; +import static com.android.internal.telephony.test.SimulatedCommands.FAKE_IMEI; +import static com.android.internal.telephony.test.SimulatedCommands.FAKE_IMEISV; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -49,13 +54,18 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.content.SharedPreferences; +import android.hardware.radio.modem.ImeiInfo; import android.os.AsyncResult; +import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.PersistableBundle; import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; import android.os.WorkSource; import android.preference.PreferenceManager; +import android.provider.DeviceConfig; import android.telecom.VideoProfile; import android.telephony.AccessNetworkConstants; import android.telephony.CarrierConfigManager; @@ -64,17 +74,24 @@ import android.telephony.CellIdentityCdma; import android.telephony.CellIdentityGsm; import android.telephony.LinkCapacityEstimate; import android.telephony.NetworkRegistrationInfo; +import android.telephony.RadioAccessFamily; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import androidx.test.filters.FlakyTest; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; +import com.android.internal.telephony.emergency.EmergencyStateTracker; import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; +import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.test.SimulatedCommands; import com.android.internal.telephony.test.SimulatedCommandsVerifier; import com.android.internal.telephony.uicc.AdnRecord; @@ -104,6 +121,7 @@ import java.util.List; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class GsmCdmaPhoneTest extends TelephonyTest { + private static final String LOG_TAG = "GsmCdmaPhoneTest"; private static final String TEST_EMERGENCY_NUMBER = "555"; // Mocked classes @@ -111,10 +129,16 @@ public class GsmCdmaPhoneTest extends TelephonyTest { private UiccSlot mUiccSlot; private CommandsInterface mMockCi; private AdnRecordCache adnRecordCache; + private DomainSelectionResolver mDomainSelectionResolver; //mPhoneUnderTest private GsmCdmaPhone mPhoneUT; + // Ideally we would use TestableDeviceConfig, but that's not doable because the Settings + // app is not currently debuggable. For now, we use the real device config and ensure that + // we reset the cellular_security namespace property to its pre-test value after every test. + private DeviceConfig.Properties mPreTestProperties; + private static final int EVENT_EMERGENCY_CALLBACK_MODE_EXIT = 1; private static final int EVENT_EMERGENCY_CALL_TOGGLE = 2; private static final int EVENT_SET_ICC_LOCK_ENABLED = 3; @@ -138,13 +162,18 @@ public class GsmCdmaPhoneTest extends TelephonyTest { @Before public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); + mPreTestProperties = DeviceConfig.getProperties( + TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE); mTestHandler = Mockito.mock(Handler.class); mUiccSlot = Mockito.mock(UiccSlot.class); mUiccPort = Mockito.mock(UiccPort.class); mMockCi = Mockito.mock(CommandsInterface.class); adnRecordCache = Mockito.mock(AdnRecordCache.class); + mDomainSelectionResolver = Mockito.mock(DomainSelectionResolver.class); doReturn(false).when(mSST).isDeviceShuttingDown(); doReturn(true).when(mImsManager).isVolteEnabledByPlatform(); + doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported(); + DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver); mPhoneUT = new GsmCdmaPhone(mContext, mSimulatedCommands, mNotifier, true, 0, PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager); @@ -162,6 +191,14 @@ public class GsmCdmaPhoneTest extends TelephonyTest { public void tearDown() throws Exception { mPhoneUT.removeCallbacksAndMessages(null); mPhoneUT = null; + DomainSelectionResolver.setDomainSelectionResolver(null); + try { + DeviceConfig.setProperties(mPreTestProperties); + } catch (DeviceConfig.BadConfigException e) { + Log.e(LOG_TAG, + "Failed to reset DeviceConfig to pre-test state. Test results may be impacted. " + + e.getMessage()); + } super.tearDown(); } @@ -958,7 +995,7 @@ public class GsmCdmaPhoneTest extends TelephonyTest { verify(mTelephonyManager).setBasebandVersionForPhone(eq(mPhoneUT.getPhoneId()), nullable(String.class)); // IMEI - assertEquals(SimulatedCommands.FAKE_IMEI, mPhoneUT.getImei()); + assertEquals(FAKE_IMEI, mPhoneUT.getImei()); // IMEISV assertEquals(SimulatedCommands.FAKE_IMEISV, mPhoneUT.getDeviceSvn()); // radio capability @@ -984,7 +1021,7 @@ public class GsmCdmaPhoneTest extends TelephonyTest { verify(mTelephonyManager, times(2)).setBasebandVersionForPhone(eq(mPhoneUT.getPhoneId()), nullable(String.class)); // device identity - assertEquals(SimulatedCommands.FAKE_IMEI, mPhoneUT.getImei()); + assertEquals(FAKE_IMEI, mPhoneUT.getImei()); assertEquals(SimulatedCommands.FAKE_IMEISV, mPhoneUT.getDeviceSvn()); assertEquals(SimulatedCommands.FAKE_ESN, mPhoneUT.getEsn()); assertEquals(SimulatedCommands.FAKE_MEID, mPhoneUT.getMeid()); @@ -1055,8 +1092,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { getVoiceCallForwardingFlag(); // invalid subId - doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubscriptionController). - getSubId(anyInt()); doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubscriptionManagerService) .getSubId(anyInt()); assertEquals(false, mPhoneUT.getCallForwardingIndicator()); @@ -1064,7 +1099,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { // valid subId, sharedPreference not present int subId1 = 0; int subId2 = 1; - doReturn(subId1).when(mSubscriptionController).getSubId(anyInt()); doReturn(subId1).when(mSubscriptionManagerService).getSubId(anyInt()); assertEquals(false, mPhoneUT.getCallForwardingIndicator()); @@ -1087,7 +1121,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { assertEquals(true, mPhoneUT.getCallForwardingIndicator()); // check for another subId - doReturn(subId2).when(mSubscriptionController).getSubId(anyInt()); doReturn(subId2).when(mSubscriptionManagerService).getSubId(anyInt()); assertEquals(false, mPhoneUT.getCallForwardingIndicator()); @@ -1097,7 +1130,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { assertEquals(true, mPhoneUT.getCallForwardingIndicator()); // switching back to previous subId, stored value should still be available - doReturn(subId1).when(mSubscriptionController).getSubId(anyInt()); doReturn(subId1).when(mSubscriptionManagerService).getSubId(anyInt()); assertEquals(true, mPhoneUT.getCallForwardingIndicator()); @@ -1260,7 +1292,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { processAllMessages(); verify(mSubscriptionManagerService, never()).getAllSubInfoList(anyString(), anyString()); - verify(mSubscriptionController, never()).getSubInfoForIccId(any()); // Have IccId defined. But expected value and current value are the same. So no RIL command // should be sent. @@ -1268,12 +1299,8 @@ public class GsmCdmaPhoneTest extends TelephonyTest { doReturn(iccId).when(mUiccSlot).getIccId(anyInt()); Message.obtain(mPhoneUT, EVENT_ICC_CHANGED, null).sendToTarget(); processAllMessages(); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService).getAllSubInfoList(anyString(), - nullable(String.class)); - } else { - verify(mSubscriptionController).getSubInfoForIccId(iccId); - } + verify(mSubscriptionManagerService).getAllSubInfoList(anyString(), + nullable(String.class)); verify(mMockCi, never()).enableUiccApplications(anyBoolean(), any()); } @@ -1309,11 +1336,12 @@ public class GsmCdmaPhoneTest extends TelephonyTest { public void testSetRadioPower() throws Exception { mPhoneUT.setRadioPower(false); verify(mSST).setRadioPowerForReason(false, false, false, false, - Phone.RADIO_POWER_REASON_USER); + TelephonyManager.RADIO_POWER_REASON_USER); // Turn on radio for emergency call. mPhoneUT.setRadioPower(true, true, false, true); - verify(mSST).setRadioPowerForReason(true, true, false, true, Phone.RADIO_POWER_REASON_USER); + verify(mSST).setRadioPowerForReason(true, true, false, true, + TelephonyManager.RADIO_POWER_REASON_USER); } @Test @@ -1321,12 +1349,12 @@ public class GsmCdmaPhoneTest extends TelephonyTest { public void testSetRadioPowerOnForTestEmergencyCall() { mPhoneUT.setRadioPower(false); verify(mSST).setRadioPowerForReason(false, false, false, false, - Phone.RADIO_POWER_REASON_USER); + TelephonyManager.RADIO_POWER_REASON_USER); mPhoneUT.setRadioPowerOnForTestEmergencyCall(false); verify(mSST).clearAllRadioOffReasons(); verify(mSST).setRadioPowerForReason(eq(true), eq(false), anyBoolean(), eq(false), - eq(Phone.RADIO_POWER_REASON_USER)); + eq(TelephonyManager.RADIO_POWER_REASON_USER)); } @Test @@ -1442,8 +1470,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { @Test public void testEventCarrierConfigChanged() { - doReturn(null).when(mSubscriptionController).getSubscriptionProperty(anyInt(), - eq(SubscriptionManager.NR_ADVANCED_CALLING_ENABLED)); doReturn(null).when(mSubscriptionManagerService).getSubscriptionProperty(anyInt(), eq(SubscriptionManager.NR_ADVANCED_CALLING_ENABLED), anyString(), anyString()); @@ -1514,11 +1540,8 @@ public class GsmCdmaPhoneTest extends TelephonyTest { @SmallTest public void testLoadAllowedNetworksFromSubscriptionDatabase_loadTheNullValue_isLoadedTrue() { int subId = 1; - doReturn(subId).when(mSubscriptionController).getSubId(anyInt()); doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt()); - doReturn(null).when(mSubscriptionController).getSubscriptionProperty(anyInt(), - eq(SubscriptionManager.ALLOWED_NETWORK_TYPES)); doReturn(null).when(mSubscriptionManagerService).getSubscriptionProperty(anyInt(), eq(SubscriptionManager.ALLOWED_NETWORK_TYPES), anyString(), anyString()); @@ -1531,11 +1554,8 @@ public class GsmCdmaPhoneTest extends TelephonyTest { @SmallTest public void testLoadAllowedNetworksFromSubscriptionDatabase_subIdNotValid_isLoadedFalse() { int subId = -1; - doReturn(subId).when(mSubscriptionController).getSubId(anyInt()); doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt()); - when(mSubscriptionController.getSubscriptionProperty(anyInt(), - eq(SubscriptionManager.ALLOWED_NETWORK_TYPES))).thenReturn(null); when(mSubscriptionManagerService.getSubscriptionProperty(anyInt(), eq(SubscriptionManager.ALLOWED_NETWORK_TYPES), anyString(), anyString())) .thenReturn(null); @@ -1545,6 +1565,83 @@ public class GsmCdmaPhoneTest extends TelephonyTest { assertEquals(false, mPhoneUT.isAllowedNetworkTypesLoadedFromDb()); } + @Test + public void testLoadAllowedNetworksFromSubscriptionDatabase_allValidData() { + int subId = 1; + doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt()); + + // 13 == TelephonyManager.NETWORK_TYPE_LTE + // NR_BITMASK == 4096 == 1 << (13 - 1) + String validSerializedNetworkMap = "user=4096,power=4096,carrier=4096,enable_2g=4096"; + SubscriptionInfoInternal si = new SubscriptionInfoInternal.Builder() + .setId(1) + .setAllowedNetworkTypesForReasons(validSerializedNetworkMap) + .build(); + doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1)); + + assertFalse(mPhoneUT.isAllowedNetworkTypesLoadedFromDb()); + mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase(); + assertTrue(mPhoneUT.isAllowedNetworkTypesLoadedFromDb()); + + for (int i = 0; i < 4; ++i) { + assertEquals(TelephonyManager.NETWORK_TYPE_BITMASK_LTE, + mPhoneUT.getAllowedNetworkTypes(i)); + } + } + + @Test + public void testLoadAllowedNetworksFromSubscriptionDatabase_invalidKeys() { + int subId = 1; + doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt()); + + // 13 == TelephonyManager.NETWORK_TYPE_LTE + // NR_BITMASK == 4096 == 1 << (13 - 1) + String validSerializedNetworkMap = + "user=4096,power=4096,carrier=4096,enable_2g=4096,-1=4096"; + SubscriptionInfoInternal si = new SubscriptionInfoInternal.Builder() + .setId(1) + .setAllowedNetworkTypesForReasons(validSerializedNetworkMap) + .build(); + doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1)); + + assertFalse(mPhoneUT.isAllowedNetworkTypesLoadedFromDb()); + mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase(); + assertTrue(mPhoneUT.isAllowedNetworkTypesLoadedFromDb()); + + for (int i = 0; i < 4; ++i) { + assertEquals(TelephonyManager.NETWORK_TYPE_BITMASK_LTE, + mPhoneUT.getAllowedNetworkTypes(i)); + } + } + + @Test + public void testLoadAllowedNetworksFromSubscriptionDatabase_invalidValues() { + int subId = 1; + doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt()); + + // 19 == TelephonyManager.NETWORK_TYPE_NR + // NR_BITMASK == 524288 == 1 << 19 + String validSerializedNetworkMap = "user=4096,power=4096,carrier=4096,enable_2g=-1"; + SubscriptionInfoInternal si = new SubscriptionInfoInternal.Builder() + .setId(1) + .setAllowedNetworkTypesForReasons(validSerializedNetworkMap) + .build(); + doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1)); + + mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase(); + + for (int i = 0; i < 3; ++i) { + assertEquals(TelephonyManager.NETWORK_TYPE_BITMASK_LTE, + mPhoneUT.getAllowedNetworkTypes(i)); + } + + long defaultAllowedNetworkTypes = RadioAccessFamily.getRafFromNetworkType( + RILConstants.PREFERRED_NETWORK_MODE); + assertEquals(defaultAllowedNetworkTypes, mPhoneUT.getAllowedNetworkTypes( + TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G)); + + } + /** * Verifies that an emergency call placed on a SIM which does NOT explicitly define a number as * an emergency call will still be placed as an emergency call. @@ -1625,7 +1722,7 @@ public class GsmCdmaPhoneTest extends TelephonyTest { doReturn(true).when(mTelephonyManager).isEmergencyNumber(anyString()); doReturn(isEmergencyPerDialedSim).when(mEmergencyNumberTracker).isEmergencyNumber( - anyString(), anyBoolean()); + anyString()); mPhoneUT.setImsPhone(mImsPhone); } @@ -1706,8 +1803,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { final SubscriptionInfoInternal si = makeSubscriptionInfoInternal( false, SubscriptionManager.USAGE_SETTING_DATA_CENTRIC); - doReturn(si.toSubscriptionInfo()).when(mSubscriptionController) - .getSubscriptionInfo(anyInt()); doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); mPhoneUT.updateUsageSetting(); @@ -1731,8 +1826,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { final SubscriptionInfoInternal si = makeSubscriptionInfoInternal( true, SubscriptionManager.USAGE_SETTING_DEFAULT); - doReturn(si.toSubscriptionInfo()).when(mSubscriptionController) - .getSubscriptionInfo(anyInt()); doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); mPhoneUT.updateUsageSetting(); @@ -1758,8 +1851,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { false, SubscriptionManager.USAGE_SETTING_DEFAULT); assertNotNull(si); - doReturn(si.toSubscriptionInfo()).when(mSubscriptionController) - .getSubscriptionInfo(anyInt()); doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); mPhoneUT.updateUsageSetting(); @@ -2069,7 +2160,7 @@ public class GsmCdmaPhoneTest extends TelephonyTest { } @Test - public void testDial_fdnCheck() throws Exception{ + public void testDial_fdnCheck() throws Exception { // dial setup mSST.mSS = mServiceState; doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState(); @@ -2097,7 +2188,7 @@ public class GsmCdmaPhoneTest extends TelephonyTest { connection = mPhoneUT.dial("1234567890", new PhoneInternalInterface.DialArgs.Builder().build()); fail("Expected CallStateException with ERROR_FDN_BLOCKED thrown."); - } catch(CallStateException e) { + } catch (CallStateException e) { assertEquals(CallStateException.ERROR_FDN_BLOCKED, e.getError()); } @@ -2105,8 +2196,383 @@ public class GsmCdmaPhoneTest extends TelephonyTest { fdnCheckCleanup(); } + @Test + public void testHandleNullCipherAndIntegrityEnabled_radioFeatureUnsupported() { + 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)); + + // 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)))); + processAllMessages(); + assertFalse(mPhoneUT.isNullCipherAndIntegritySupported()); + } + + @Test + public void testHandleNullCipherAndIntegrityEnabled_radioSupportsFeature() { + 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)); + + // 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(); + assertTrue(mPhoneUT.isNullCipherAndIntegritySupported()); + } + + @Test + public void testHandleNullCipherAndIntegrityEnabled_featureFlagOn() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY, + TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(), + false); + mPhoneUT.mCi = mMockCi; + + 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)); + } + + @Test + public void testHandleNullCipherAndIntegrityEnabled_featureFlagOff() { + mPhoneUT.mCi = mMockCi; + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY, + TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.FALSE.toString(), + false); + + mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_RADIO_AVAILABLE, + new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null))); + processAllMessages(); + + verify(mMockCi, times(0)).setNullCipherAndIntegrityEnabled(anyBoolean(), + any(Message.class)); + } + public void fdnCheckCleanup() { doReturn(false).when(mUiccCardApplication3gpp).getIccFdnAvailable(); doReturn(false).when(mUiccCardApplication3gpp).getIccFdnEnabled(); } + + @Test + @SmallTest + public void testTriggerImsDeregistration() throws Exception { + replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone); + + mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_IMS_DEREGISTRATION_TRIGGERED, + new AsyncResult(null, + new int[]{ImsRegistrationImplBase.REASON_SIM_REFRESH}, null))); + processAllMessages(); + + verify(mImsPhone, times(1)).triggerImsDeregistration( + eq(ImsRegistrationImplBase.REASON_SIM_REFRESH)); + } + + @Test + public void testDomainSelectionEmergencyCallCs() throws CallStateException { + setupEmergencyCallScenario(false /* USE_ONLY_DIALED_SIM_ECC_LIST */, + false /* isEmergencyOnDialedSim */); + + Bundle extras = new Bundle(); + extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS); + ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder() + .setIntentExtras(extras) + .build(); + mPhoneUT.dial(TEST_EMERGENCY_NUMBER, dialArgs); + + verify(mCT).dialGsm(anyString(), any(PhoneInternalInterface.DialArgs.class)); + } + + @Test + public void testDomainSelectionEmergencyCallPs() throws CallStateException { + setupEmergencyCallScenario(false /* USE_ONLY_DIALED_SIM_ECC_LIST */, + false /* isEmergencyOnDialedSim */); + + doReturn(false).when(mImsPhone).isImsAvailable(); + + Bundle extras = new Bundle(); + extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_PS); + ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder() + .setIntentExtras(extras) + .build(); + mPhoneUT.dial(TEST_EMERGENCY_NUMBER, dialArgs); + + verify(mImsPhone).dial(anyString(), any(PhoneInternalInterface.DialArgs.class)); + + extras = dialArgs.intentExtras; + + assertFalse(extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)); + } + + @Test + public void testDomainSelectionEmergencyCallNon3GppPs() throws CallStateException { + setupEmergencyCallScenario(false /* USE_ONLY_DIALED_SIM_ECC_LIST */, + false /* isEmergencyOnDialedSim */); + + doReturn(false).when(mImsPhone).isImsAvailable(); + + Bundle extras = new Bundle(); + extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, PhoneConstants.DOMAIN_NON_3GPP_PS); + ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder() + .setIntentExtras(extras) + .build(); + mPhoneUT.dial(TEST_EMERGENCY_NUMBER, dialArgs); + + verify(mImsPhone).dial(anyString(), any(PhoneInternalInterface.DialArgs.class)); + + extras = dialArgs.intentExtras; + + assertTrue(extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)); + assertEquals(String.valueOf(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN), + extras.getString(ImsCallProfile.EXTRA_CALL_RAT_TYPE)); + } + + @Test + public void testDomainSelectionDialCs() throws Exception { + doReturn(true).when(mImsPhone).isImsAvailable(); + doReturn(true).when(mImsManager).isVolteEnabledByPlatform(); + doReturn(true).when(mImsManager).isEnhanced4gLteModeSettingEnabledByUser(); + doReturn(true).when(mImsManager).isNonTtyOrTtyOnVolteEnabled(); + doReturn(true).when(mImsPhone).isVoiceOverCellularImsEnabled(); + doReturn(true).when(mImsPhone).isUtEnabled(); + + replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone); + + Bundle extras = new Bundle(); + extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS); + ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder() + .setIntentExtras(extras) + .build(); + Connection connection = mPhoneUT.dial("1234567890", dialArgs); + verify(mCT).dialGsm(eq("1234567890"), any(PhoneInternalInterface.DialArgs.class)); + } + + @Test + public void testDomainSelectionDialPs() throws Exception { + mSST.mSS = mServiceState; + doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState(); + + mCT.mForegroundCall = mGsmCdmaCall; + mCT.mBackgroundCall = mGsmCdmaCall; + mCT.mRingingCall = mGsmCdmaCall; + doReturn(GsmCdmaCall.State.IDLE).when(mGsmCdmaCall).getState(); + + replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone); + + Bundle extras = new Bundle(); + extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_PS); + ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder() + .setIntentExtras(extras) + .build(); + Connection connection = mPhoneUT.dial("1234567890", dialArgs); + verify(mImsPhone).dial(eq("1234567890"), any(PhoneInternalInterface.DialArgs.class)); + } + + @Test + public void getImeiType_primary() { + Message message = mPhoneUT.obtainMessage(Phone.EVENT_GET_DEVICE_IMEI_DONE); + ImeiInfo imeiInfo = new ImeiInfo(); + imeiInfo.imei = FAKE_IMEI; + imeiInfo.svn = FAKE_IMEISV; + imeiInfo.type = ImeiInfo.ImeiType.PRIMARY; + AsyncResult.forMessage(message, imeiInfo, null); + mPhoneUT.handleMessage(message); + assertEquals(Phone.IMEI_TYPE_PRIMARY, mPhoneUT.getImeiType()); + assertEquals(FAKE_IMEI, mPhoneUT.getImei()); + } + + @Test + public void getImeiType_Secondary() { + Message message = mPhoneUT.obtainMessage(Phone.EVENT_GET_DEVICE_IMEI_DONE); + ImeiInfo imeiInfo = new ImeiInfo(); + imeiInfo.imei = FAKE_IMEI; + imeiInfo.svn = FAKE_IMEISV; + imeiInfo.type = ImeiInfo.ImeiType.SECONDARY; + AsyncResult.forMessage(message, imeiInfo, null); + mPhoneUT.handleMessage(message); + assertEquals(Phone.IMEI_TYPE_SECONDARY, mPhoneUT.getImeiType()); + assertEquals(FAKE_IMEI, mPhoneUT.getImei()); + } + + @Test + public void getImei() { + assertTrue(mPhoneUT.isPhoneTypeGsm()); + Message message = mPhoneUT.obtainMessage(Phone.EVENT_RADIO_AVAILABLE); + mPhoneUT.handleMessage(message); + verify(mSimulatedCommandsVerifier, times(2)).getImei(nullable(Message.class)); + } + + @Test + public void testSetAllowedNetworkTypes_admin2gRestrictionHonored() throws Exception { + // circumvent loading/saving to sim db. it's not behavior under test. + TelephonyManager.setupISubForTest(Mockito.mock(SubscriptionManagerService.class)); + TelephonyManager.enableServiceHandleCaching(); + mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase(); + + // 2g is disabled by admin + UserManager userManagerMock = mContext.getSystemService(UserManager.class); + when(userManagerMock.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(true); + mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)); + + // carrier requests 2g to be enabled + mPhoneUT.setAllowedNetworkTypes(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER, + TelephonyManager.NETWORK_CLASS_BITMASK_2G, null); + + // Assert that 2g was not passed as an allowed network type to the modem + ArgumentCaptor captureBitMask = ArgumentCaptor.forClass(Integer.class); + // One call for the admin restriction update, another by this test + verify(mSimulatedCommandsVerifier, times(2)).setAllowedNetworkTypesBitmap( + captureBitMask.capture(), + nullable(Message.class)); + assertEquals(0, captureBitMask.getValue() & TelephonyManager.NETWORK_CLASS_BITMASK_2G); + } + + @Test + @SmallTest + public void testEcbm() throws Exception { + assertFalse(mPhoneUT.isInEcm()); + + mPhoneUT.handleMessage(mPhoneUT.obtainMessage( + GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER)); + + assertTrue(mPhoneUT.isInEcm()); + verifyEcbmIntentWasSent(1 /*times*/, true /*inEcm*/); + // verify that wakeLock is acquired in ECM + assertTrue(mPhoneUT.getWakeLock().isHeld()); + + boolean isTestingEmergencyCallbackMode = true; + replaceInstance(GsmCdmaPhone.class, "mIsTestingEmergencyCallbackMode", mPhoneUT, + isTestingEmergencyCallbackMode); + mPhoneUT.exitEmergencyCallbackMode(); + processAllMessages(); + + assertFalse(mPhoneUT.isInEcm()); + verifyEcbmIntentWasSent(2/*times*/, false /*inEcm*/); + // verify wakeLock released + assertFalse(mPhoneUT.getWakeLock().isHeld()); + } + + private void verifyEcbmIntentWasSent(int times, boolean isInEcm) throws Exception { + // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, atLeast(times)).sendStickyBroadcastAsUser(intentArgumentCaptor.capture(), + any()); + + Intent intent = intentArgumentCaptor.getValue(); + assertNotNull(intent); + assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction()); + assertEquals(isInEcm, intent.getBooleanExtra( + TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)); + } + + @Test + @SmallTest + public void testEcbmWhenDomainSelectionEnabled() throws Exception { + DomainSelectionResolver dsResolver = Mockito.mock(DomainSelectionResolver.class); + doReturn(true).when(dsResolver).isDomainSelectionSupported(); + DomainSelectionResolver.setDomainSelectionResolver(dsResolver); + + mPhoneUT.handleMessage(mPhoneUT.obtainMessage( + GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER)); + + boolean isTestingEmergencyCallbackMode = true; + replaceInstance(GsmCdmaPhone.class, "mIsTestingEmergencyCallbackMode", mPhoneUT, + isTestingEmergencyCallbackMode); + mPhoneUT.exitEmergencyCallbackMode(); + processAllMessages(); + + verify(mContext, never()).sendStickyBroadcastAsUser(any(), any()); + } + + @Test + @SmallTest + public void testEcbmOnModemResetForNonGsmPhone() throws Exception { + switchToCdma(); + assertFalse(mPhoneUT.isInEcm()); + + mPhoneUT.handleMessage(mPhoneUT.obtainMessage( + GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER)); + + assertTrue(mPhoneUT.isInEcm()); + + Message m = mPhoneUT.obtainMessage(GsmCdmaPhone.EVENT_MODEM_RESET); + AsyncResult.forMessage(m); + mPhoneUT.handleMessage(m); + + assertFalse(mPhoneUT.isInEcm()); + verifyEcbmIntentWasSent(2 /*times*/, false /*inEcm*/); + } + + @Test + @SmallTest + public void testEcbmOnModemResetWhenDomainSelectionEnabled() throws Exception { + DomainSelectionResolver dsResolver = Mockito.mock(DomainSelectionResolver.class); + doReturn(true).when(dsResolver).isDomainSelectionSupported(); + DomainSelectionResolver.setDomainSelectionResolver(dsResolver); + + EmergencyStateTracker est = Mockito.mock(EmergencyStateTracker.class); + doReturn(true).when(est).isInEcm(); + replaceInstance(EmergencyStateTracker.class, "INSTANCE", null, est); + + GsmCdmaPhone spyPhone = spy(mPhoneUT); + doReturn(true).when(spyPhone).isInEcm(); + mPhoneUT.handleMessage(mPhoneUT.obtainMessage(GsmCdmaPhone.EVENT_MODEM_RESET)); + + verify(est).exitEmergencyCallbackMode(); + } + + @Test + public void testGetUserHandle() { + UserHandle userHandle = new UserHandle(123); + doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(anyInt()); + assertEquals(userHandle, mPhoneUT.getUserHandle()); + + doReturn(null).when(mSubscriptionManager).getSubscriptionUserHandle(anyInt()); + assertNull(mPhoneUT.getUserHandle()); + + doThrow(IllegalArgumentException.class).when(mSubscriptionManager) + .getSubscriptionUserHandle(anyInt()); + assertNull(mPhoneUT.getUserHandle()); + } + + @Test + public void testResetNetworkSelectionModeOnSimSwap() { + // Set current network selection manual mode. + mSimulatedCommands.setNetworkSelectionModeManual("123", 0, null); + clearInvocations(mSimulatedCommandsVerifier); + + // SIM loaded. + Intent simLoadedIntent = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED); + simLoadedIntent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId()); + simLoadedIntent.putExtra(TelephonyManager.EXTRA_SIM_STATE, + TelephonyManager.SIM_STATE_LOADED); + mContext.sendBroadcast(simLoadedIntent); + + processAllFutureMessages(); + // Verify set network selection mode to be AUTO + verify(mSimulatedCommandsVerifier).getNetworkSelectionMode(any(Message.class)); + verify(mSimulatedCommandsVerifier).setNetworkSelectionModeAutomatic(any(Message.class)); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java index 29b128d508c8c769b5939fe11dfbe3b1fd3be91a..4012e98cae62e45b8dfeff2e266ddc8b6f2f4952 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,6 +34,8 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.internal.telephony.emergency.EmergencyNumberTracker; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -50,6 +53,8 @@ public class IccSmsInterfaceManagerTest extends TelephonyTest { // Mocked classes private SmsPermissions mMockSmsPermissions; + protected EmergencyNumberTracker mEmergencyNumberTracker2; + protected IccSmsInterfaceManager.PhoneFactoryProxy mPhoneFactoryProxy; @Before public void setUp() throws Exception { @@ -57,6 +62,12 @@ public class IccSmsInterfaceManagerTest extends TelephonyTest { mMockSmsPermissions = mock(SmsPermissions.class); mIccSmsInterfaceManager = new IccSmsInterfaceManager(mPhone, mContext, mAppOpsManager, mSmsDispatchersController, mMockSmsPermissions); + + mPhoneFactoryProxy = mock(IccSmsInterfaceManager.PhoneFactoryProxy.class); + mIccSmsInterfaceManager.setPhoneFactoryProxy(mPhoneFactoryProxy); + + mEmergencyNumberTracker2 = mock(EmergencyNumberTracker.class); + doReturn(mEmergencyNumberTracker2).when(mPhone2).getEmergencyNumberTracker(); } @After @@ -144,4 +155,22 @@ public class IccSmsInterfaceManagerTest extends TelephonyTest { fail("getSmscLatch.await interrupted"); } } + + @Test + public void testNotifyIfOutgoingEmergencySmsWithDualSim() { + //Replicate Dual-SIM: + Phone [] phones = {mPhone, mPhone2}; + when(mPhoneFactoryProxy.getPhones()).thenReturn(phones); + doReturn(1).when(mPhone).getPhoneId(); + doReturn(2).when(mPhone2).getPhoneId(); + + //Replicate behavior when a number is an emergency number + // on the secondary SIM but not on the default SIM: + when(mPhone.getEmergencyNumberTracker().getEmergencyNumber(any())).thenReturn(null); + when(mEmergencyNumberTracker2.getEmergencyNumber(any())) + .thenReturn(getTestEmergencyNumber()); + + mIccSmsInterfaceManager.notifyIfOutgoingEmergencySms("1234"); + verify(mEmergencyNumberTracker2).getEmergencyNumber("1234"); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java index fd1c5e0007ec0d35ddc52372ed2f545feff2aada..1c44772d88252c8be381cf19132c4f36c1d9fade 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java @@ -29,10 +29,13 @@ import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; import android.telephony.SmsMessage; import android.telephony.ims.stub.ImsSmsImplBase; import android.test.suitebuilder.annotation.SmallTest; @@ -64,7 +67,9 @@ public class ImsSmsDispatcherTest extends TelephonyTest { private FeatureConnector.Listener mImsManagerListener; private HashMap mTrackerData; private ImsSmsDispatcher mImsSmsDispatcher; + PersistableBundle mBundle = new PersistableBundle(); private static final int SUB_0 = 0; + private static final String TAG = "ImsSmsDispatcherTest"; @Before public void setUp() throws Exception { @@ -87,6 +92,7 @@ public class ImsSmsDispatcherTest extends TelephonyTest { when(mSmsDispatchersController.isIms()).thenReturn(true); mTrackerData = new HashMap<>(1); when(mSmsTracker.getData()).thenReturn(mTrackerData); + verify(mSmsDispatchersController).setImsManager(mImsManager); } @After @@ -97,6 +103,85 @@ public class ImsSmsDispatcherTest extends TelephonyTest { super.tearDown(); } + /** + * Send Memory Availability Notification and verify that the token is correct. + */ + @Test + @SmallTest + public void testOnMemoryAvailable() throws Exception { + int token = mImsSmsDispatcher.mNextToken.get(); + //Send SMMA + mImsSmsDispatcher.onMemoryAvailable(); + assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get()); + verify(mImsManager).onMemoryAvailable(eq(token + 1)); + } + + /** + * Receive SEND_STATUS_ERROR_RETRY with onMemoryAvailableResult Api and check if + * sending SMMA Notification is retried once + */ + @Test + @SmallTest + public void testOnMemoryAvailableResultErrorRetry() throws Exception { + int token = mImsSmsDispatcher.mNextToken.get(); + //Send SMMA + mImsSmsDispatcher.onMemoryAvailable(); + assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get()); + verify(mImsManager).onMemoryAvailable(eq(token + 1)); + // Retry over IMS + mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 1, + ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, SmsResponse.NO_ERROR_CODE); + waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200); + processAllMessages(); + verify(mImsManager).onMemoryAvailable(eq(token + 2)); + //2nd Failure should not retry + mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 2, + ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, SmsResponse.NO_ERROR_CODE); + waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200); + processAllMessages(); + verify(mImsManager, times(0)).onMemoryAvailable(eq(token + 3)); + + } + /** + * Receive SEND_STATUS_OK with onMemoryAvailableResult Api and check if + * sending SMMA Notification behaviour is correct + */ + @Test + @SmallTest + public void testOnMemoryAvailableResultSuccess() throws Exception { + int token = mImsSmsDispatcher.mNextToken.get(); + //Send SMMA + mImsSmsDispatcher.onMemoryAvailable(); + assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get()); + verify(mImsManager).onMemoryAvailable(eq(token + 1)); + // Retry over IMS + mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 1, + ImsSmsImplBase.SEND_STATUS_OK, SmsResponse.NO_ERROR_CODE); + waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200); + processAllMessages(); + verify(mImsManager, times(0)).onMemoryAvailable(eq(token + 2)); + + } + /** + * Receive SEND_STATUS_ERROR with onMemoryAvailableResult Api and check if + * sending SMMA Notification behaviour is correct + */ + @Test + @SmallTest + public void testOnMemoryAvailableResultError() throws Exception { + int token = mImsSmsDispatcher.mNextToken.get(); + //Send SMMA + mImsSmsDispatcher.onMemoryAvailable(); + assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get()); + verify(mImsManager).onMemoryAvailable(eq(token + 1)); + // Retry over IMS + mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 1, + ImsSmsImplBase.SEND_STATUS_ERROR, SmsResponse.NO_ERROR_CODE); + waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200); + processAllMessages(); + verify(mImsManager, times(0)).onMemoryAvailable(eq(token + 2)); + + } /** * Send an SMS and verify that the token and PDU is correct. */ @@ -155,6 +240,13 @@ public class ImsSmsDispatcherTest extends TelephonyTest { @SmallTest public void testErrorImsRetry() throws Exception { int token = mImsSmsDispatcher.mNextToken.get(); + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT, + 2000); + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 3); + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_MAX_RETRY_COUNT_INT, 3); mTrackerData.put("pdu", com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, "+15555551212", "Test", false).encodedMessage); when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP); @@ -230,7 +322,10 @@ public class ImsSmsDispatcherTest extends TelephonyTest { @Test public void testSendSmswithMessageRef() throws Exception { int token = mImsSmsDispatcher.mNextToken.get(); - int messageRef = mImsSmsDispatcher.nextMessageRef() + 1; + int messageRef = mImsSmsDispatcher.nextMessageRef(); + if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) { + messageRef += 1; + } when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP); when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); @@ -246,7 +341,10 @@ public class ImsSmsDispatcherTest extends TelephonyTest { @Test public void testFallbackGsmRetrywithMessageRef() throws Exception { int token = mImsSmsDispatcher.mNextToken.get(); - int messageRef = mImsSmsDispatcher.nextMessageRef() + 1; + int messageRef = mImsSmsDispatcher.nextMessageRef(); + if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) { + messageRef += 1; + } when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP); when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); @@ -265,13 +363,28 @@ public class ImsSmsDispatcherTest extends TelephonyTest { ArgumentCaptor captor = ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class); verify(mSmsDispatchersController).sendRetrySms(captor.capture()); - assertTrue(messageRef + 1 == captor.getValue().mMessageRef); + if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) { + assertTrue(messageRef + 1 == captor.getValue().mMessageRef); + } else { + assertTrue(messageRef == captor.getValue().mMessageRef); + } } @Test public void testErrorImsRetrywithMessageRef() throws Exception { int token = mImsSmsDispatcher.mNextToken.get(); - int messageRef = mImsSmsDispatcher.nextMessageRef() + 1; + int messageRef = mImsSmsDispatcher.nextMessageRef(); + if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) { + messageRef += 1; + } + + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT, + 2000); + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 3); + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_MAX_RETRY_COUNT_INT, 3); when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP); when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor(); @@ -293,7 +406,73 @@ public class ImsSmsDispatcherTest extends TelephonyTest { verify(mImsManager).sendSms(eq(token + 2), eq(messageRef), eq(SmsMessage.FORMAT_3GPP), nullable(String.class), eq(true), byteCaptor.capture()); byte[] pdu = byteCaptor.getValue(); - assertEquals(pdu[1], messageRef); + assertEquals(messageRef, pdu[1]); + assertEquals(0x04, (pdu[0] & 0x04)); + } + + @Test + public void testErrorImsRetrywithRetryConfig() throws Exception { + int token = mImsSmsDispatcher.mNextToken.get(); + int messageRef = mImsSmsDispatcher.nextMessageRef(); + if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) { + messageRef += 1; + } + + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT, + 3000); + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 2); + mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms + .KEY_SMS_MAX_RETRY_COUNT_INT, 3); + when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP); + when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); + doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor(); + mImsSmsDispatcher.sendText("+15555551212", null, "Retry test", + null, null, null, null, false, + -1, false, -1, false, 0); + verify(mImsManager).sendSms(eq(token + 1), eq(messageRef), eq(SmsMessage.FORMAT_3GPP), + nullable(String.class), eq(false), (byte[]) any()); + assertEquals(2, mImsSmsDispatcher.getMaxRetryCountOverIms()); + assertEquals(3, mImsSmsDispatcher.getMaxSmsRetryCount()); + assertEquals(3000, mImsSmsDispatcher.getSmsRetryDelayValue()); + // Retry over IMS + mImsSmsDispatcher.getSmsListener().onSendSmsResult(token + 1, messageRef, + ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0, SmsResponse.NO_ERROR_CODE); + waitForMs(mImsSmsDispatcher.getSmsRetryDelayValue() + 200); + processAllMessages(); + + // Make sure tpmr value is same and retry bit set + ArgumentCaptor byteCaptor = ArgumentCaptor.forClass(byte[].class); + verify(mImsManager).sendSms(eq(token + 2), eq(messageRef), eq(SmsMessage.FORMAT_3GPP), + nullable(String.class), eq(true), byteCaptor.capture()); + byte[] pdu = byteCaptor.getValue(); + assertEquals(messageRef, pdu[1]); assertEquals(0x04, (pdu[0] & 0x04)); + // Retry over IMS for the second time + mImsSmsDispatcher.getSmsListener().onSendSmsResult(token + 2, messageRef, + ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0, SmsResponse.NO_ERROR_CODE); + waitForMs(mImsSmsDispatcher.getSmsRetryDelayValue() + 200); + processAllMessages(); + // Make sure tpmr value is same and retry bit set + ArgumentCaptor byteCaptor2 = ArgumentCaptor.forClass(byte[].class); + verify(mImsManager).sendSms(eq(token + 3), eq(messageRef), eq(SmsMessage.FORMAT_3GPP), + nullable(String.class), eq(true), byteCaptor2.capture()); + byte[] pdu2 = byteCaptor2.getValue(); + assertEquals(messageRef, pdu2[1]); + assertEquals(0x04, (pdu2[0] & 0x04)); + + mImsSmsDispatcher.getSmsListener().onSendSmsResult(token + 3, messageRef, + ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0, SmsResponse.NO_ERROR_CODE); + waitForMs(mImsSmsDispatcher.getSmsRetryDelayValue() + 200); + processAllMessages(); + // Make sure tpmr value is same and retry bit set + ArgumentCaptor captor = + ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class); + // Ensure GsmSmsDispatcher calls sendSms + verify(mSmsDispatchersController).sendRetrySms(captor.capture()); + + assertNotNull(captor.getValue()); + assertTrue(captor.getValue().mRetryCount > 0); } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java index b11b94ea0d74fa4a34ddd89841e5a80cc7d485b0..d251cf5f0e3b9a46d55651ceb9f5622c0b13f287 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java @@ -24,8 +24,6 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.test.suitebuilder.annotation.SmallTest; -import androidx.test.InstrumentationRegistry; - import com.android.internal.util.HexDump; import org.junit.After; @@ -34,7 +32,7 @@ import org.junit.Test; import java.util.Arrays; -public class InboundSmsTrackerTest { +public class InboundSmsTrackerTest extends TelephonyTest { InboundSmsTracker mInboundSmsTracker; private static final byte[] FAKE_PDU = new byte[]{1, 2, 3}; @@ -50,7 +48,8 @@ public class InboundSmsTrackerTest { @Before public void setUp() throws Exception { - mInboundSmsTracker = new InboundSmsTracker(InstrumentationRegistry.getContext(), + super.setUp(getClass().getSimpleName()); + mInboundSmsTracker = new InboundSmsTracker(mContext, FAKE_PDU, FAKE_TIMESTAMP, FAKE_DEST_PORT, false, FAKE_ADDRESS, FAKE_DISPLAY_ADDRESS, FAKE_REFERENCE_NUMBER, FAKE_SEQUENCE_NUMBER, FAKE_MESSAGE_COUNT, false, FAKE_MESSAGE_BODY, false /* isClass0 */, FAKE_SUBID, @@ -58,8 +57,9 @@ public class InboundSmsTrackerTest { } @After - public void tearDown() { + public void tearDown() throws Exception { mInboundSmsTracker = null; + super.tearDown(); } public static MatrixCursor createFakeCursor() { @@ -108,8 +108,7 @@ public class InboundSmsTrackerTest { @SmallTest public void testInitializationFromDb() { Cursor cursor = createFakeCursor(); - mInboundSmsTracker = new InboundSmsTracker(InstrumentationRegistry.getContext(), - cursor, false); + mInboundSmsTracker = new InboundSmsTracker(mContext, cursor, false); cursor.close(); testInitialization(); } diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java index 372733d07efc404fcd408ee03f0a6e224ae7375b..f4c19d974aa508449561abd7506644592fb7f43d 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java @@ -136,6 +136,7 @@ public class MultiSimSettingControllerTest extends TelephonyTest { mSubInfo[subId] = new SubscriptionInfoInternal.Builder(mSubInfo[subId]) .setSimSlotIndex(simSlotIndex).build(); } + private void sendCarrierConfigChanged(int phoneId, int subId) { mCarrierConfigChangeListener.onCarrierConfigChanged(phoneId, subId, TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID); @@ -144,7 +145,6 @@ public class MultiSimSettingControllerTest extends TelephonyTest { @Before public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); - enableSubscriptionManagerService(true); initializeSubs(); mPhoneMock1 = mock(Phone.class); mPhoneMock2 = mock(Phone.class); @@ -156,6 +156,9 @@ public class MultiSimSettingControllerTest extends TelephonyTest { doReturn(mPhone).when(mPhone).getImsPhone(); mServiceManagerMockedServices.put("isub", mIBinder); + doReturn(mSubscriptionManagerService).when(mIBinder) + .queryLocalInterface(anyString()); + // Default configuration: // DSDS device. // Sub 1 is the default sub. @@ -243,12 +246,10 @@ public class MultiSimSettingControllerTest extends TelephonyTest { }).when(mPhoneMock2).getSubId(); replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); - // Capture listener to emulate the carrier config change notification used later ArgumentCaptor listenerArgumentCaptor = ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class); - mMultiSimSettingControllerUT = new MultiSimSettingController(mContext, - mSubscriptionController); + mMultiSimSettingControllerUT = new MultiSimSettingController(mContext); processAllMessages(); verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(), listenerArgumentCaptor.capture()); @@ -463,15 +464,15 @@ public class MultiSimSettingControllerTest extends TelephonyTest { markSubscriptionInactive(1); mMultiSimSettingControllerUT.notifySubscriptionInfoChanged(); processAllMessages(); + sendCarrierConfigChanged(1, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + processAllMessages(); + verify(mSubscriptionManagerService).setDefaultDataSubId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); verify(mSubscriptionManagerService).setDefaultSmsSubId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); verify(mSubscriptionManagerService, never()).setDefaultVoiceSubId(anyInt()); - sendCarrierConfigChanged(1, SubscriptionManager.INVALID_SUBSCRIPTION_ID); - processAllMessages(); - // Verify intent sent to select sub 2 as default for all types. Intent intent = captureBroadcastIntent(); assertEquals(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED, intent.getAction()); diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java index a275bb8b59b1280a82636f352ea8cf78d796c78f..8dc8ea710fb4219a1aa806e968acf0b3f885573a 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java @@ -18,6 +18,7 @@ package com.android.internal.telephony; import static junit.framework.Assert.assertEquals; +import android.compat.testing.PlatformCompatChangeRule; import android.os.Parcel; import android.telephony.AccessNetworkConstants; import android.telephony.CellIdentityLte; @@ -27,13 +28,21 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import java.util.Arrays; /** Unit tests for {@link NetworkRegistrationInfo}. */ public class NetworkRegistrationInfoTest { + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + @Test @SmallTest public void testParcel() { @@ -87,4 +96,47 @@ public class NetworkRegistrationInfoTest { assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING, nri.getNetworkRegistrationState()); } + + @Test + @DisableCompatChanges({NetworkRegistrationInfo.RETURN_REGISTRATION_STATE_EMERGENCY}) + public void testReturnRegistrationStateEmergencyDisabled() { + // LTE + NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE) + .build(); + + assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_DENIED, nri.getRegistrationState()); + + // NR + nri = new NetworkRegistrationInfo.Builder() + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR) + .build(); + + assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, + nri.getRegistrationState()); + } + + @Test + @EnableCompatChanges({NetworkRegistrationInfo.RETURN_REGISTRATION_STATE_EMERGENCY}) + public void testReturnRegistrationStateEmergencyEnabled() { + // LTE + NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE) + .build(); + + assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY, + nri.getRegistrationState()); + + // NR + nri = new NetworkRegistrationInfo.Builder() + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR) + .build(); + + assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY, + nri.getRegistrationState()); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java index 2b487f2f4a1b41c313c9c33d815f1871fe07c1e0..4b91207e3125b2cecfd2bf7f76dc98b09682f3cd 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java @@ -20,12 +20,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; -import android.content.Intent; import android.os.AsyncResult; import android.os.Handler; import android.os.IPowerManager; @@ -50,7 +50,6 @@ import com.android.internal.util.StateMachine; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -61,21 +60,10 @@ import java.util.List; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -@Ignore("b/240911460") public class NetworkTypeControllerTest extends TelephonyTest { - // private constants copied over from NetworkTypeController - private static final int EVENT_DATA_RAT_CHANGED = 2; - private static final int EVENT_NR_STATE_CHANGED = 3; - private static final int EVENT_NR_FREQUENCY_CHANGED = 4; - private static final int EVENT_PHYSICAL_LINK_STATUS_CHANGED = 5; - private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6; - private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 10; - private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 11; - private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 13; - private NetworkTypeController mNetworkTypeController; private PersistableBundle mBundle; - private DataNetworkControllerCallback mDataNetworkControllerCallback; + private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; private IState getCurrentState() throws Exception { Method method = StateMachine.class.getDeclaredMethod("getCurrentState"); @@ -89,11 +77,12 @@ public class NetworkTypeControllerTest extends TelephonyTest { method.invoke(mNetworkTypeController); } - private void broadcastCarrierConfigs() { - Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); - intent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId()); - intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId()); - mContext.sendBroadcast(intent); + private void sendCarrierConfigChanged() { + if (mCarrierConfigChangeListener != null) { + mCarrierConfigChangeListener.onCarrierConfigChanged(mPhone.getPhoneId(), + mPhone.getSubId(), TelephonyManager.UNKNOWN_CARRIER_ID, + TelephonyManager.UNKNOWN_CARRIER_ID); + } processAllMessages(); } @@ -104,9 +93,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING, "connected_mmwave:5G_Plus,connected:5G,not_restricted_rrc_idle:5G," + "not_restricted_rrc_con:5G"); - mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF03); mBundle.putInt(CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); replaceInstance(Handler.class, "mLooper", mDisplayInfoController, Looper.myLooper()); doReturn(RadioAccessFamily.getRafFromNetworkType( @@ -115,14 +103,14 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported( TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED); doReturn(new int[] {0}).when(mServiceState).getCellBandwidths(); + // Capture listener to emulate the carrier config change notification used later + ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class); mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController); processAllMessages(); - - ArgumentCaptor dataNetworkControllerCallbackCaptor = - ArgumentCaptor.forClass(DataNetworkControllerCallback.class); - verify(mDataNetworkController).registerDataNetworkControllerCallback( - dataNetworkControllerCallbackCaptor.capture()); - mDataNetworkControllerCallback = dataNetworkControllerCallbackCaptor.getValue(); + verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(), + listenerArgumentCaptor.capture()); + mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0); } @After @@ -234,7 +222,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn("test_patternShowAdvanced").when(mServiceState).getOperatorAlphaLongRaw(); mBundle.putString(CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, ".*_patternShowAdvanced"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); updateOverrideNetworkType(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO, mNetworkTypeController.getOverrideNetworkType()); @@ -259,8 +247,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { @Test public void testTransitionToCurrentStateLegacy() throws Exception { assertEquals("DefaultState", getCurrentState().getName()); - - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("legacy", getCurrentState().getName()); } @@ -269,8 +256,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { public void testTransitionToCurrentStateRestricted() throws Exception { assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState(); - - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("restricted", getCurrentState().getName()); } @@ -279,9 +265,9 @@ public class NetworkTypeControllerTest extends TelephonyTest { public void testTransitionToCurrentStateIdle() throws Exception { assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null)); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_idle", getCurrentState().getName()); } @@ -296,8 +282,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); setPhysicalLinkStatus(false); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_idle", getCurrentState().getName()); } @@ -309,17 +295,15 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported( TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED); mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); processAllMessages(); assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null)); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); - + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); - assertEquals("not_restricted_rrc_idle", getCurrentState().getName()); } @@ -327,9 +311,9 @@ public class NetworkTypeControllerTest extends TelephonyTest { public void testTransitionToCurrentStateLteConnected() throws Exception { assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null)); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_con", getCurrentState().getName()); } @@ -340,14 +324,14 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported( TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED); mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); processAllMessages(); assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); setPhysicalLinkStatus(true); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_con", getCurrentState().getName()); } @@ -360,17 +344,15 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported( TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED); mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); processAllMessages(); assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null)); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); - + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); - assertEquals("not_restricted_rrc_con", getCurrentState().getName()); } @@ -379,7 +361,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); } @@ -390,7 +372,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected_mmwave", getCurrentState().getName()); } @@ -410,9 +392,10 @@ public class NetworkTypeControllerTest extends TelephonyTest { List lastPhysicalChannelConfigList = new ArrayList<>(); lastPhysicalChannelConfigList.add(physicalChannelConfig); doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected_mmwave", getCurrentState().getName()); } @@ -432,9 +415,10 @@ public class NetworkTypeControllerTest extends TelephonyTest { List lastPhysicalChannelConfigList = new ArrayList<>(); lastPhysicalChannelConfigList.add(physicalChannelConfig); doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); } @@ -444,10 +428,9 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("DefaultState", getCurrentState().getName()); doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected_mmwave", getCurrentState().getName()); } @@ -459,9 +442,14 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF03); - broadcastCarrierConfigs(); - mDataNetworkControllerCallback.onNrAdvancedCapableByPcoChanged(false); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + sendCarrierConfigChanged(); + + ArgumentCaptor dataNetworkControllerCallbackCaptor = + ArgumentCaptor.forClass(DataNetworkControllerCallback.class); + verify(mDataNetworkController).registerDataNetworkControllerCallback( + dataNetworkControllerCallbackCaptor.capture()); + DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue(); + callback.onNrAdvancedCapableByPcoChanged(false); processAllMessages(); assertEquals("connected", getCurrentState().getName()); } @@ -473,10 +461,14 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF00); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); - mDataNetworkControllerCallback.onNrAdvancedCapableByPcoChanged(false); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + ArgumentCaptor dataNetworkControllerCallbackCaptor = + ArgumentCaptor.forClass(DataNetworkControllerCallback.class); + verify(mDataNetworkController).registerDataNetworkControllerCallback( + dataNetworkControllerCallbackCaptor.capture()); + DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue(); + callback.onNrAdvancedCapableByPcoChanged(false); processAllMessages(); assertEquals("connected", getCurrentState().getName()); } @@ -488,11 +480,14 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF03); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); - processAllMessages(); - mDataNetworkControllerCallback.onNrAdvancedCapableByPcoChanged(true); + ArgumentCaptor dataNetworkControllerCallbackCaptor = + ArgumentCaptor.forClass(DataNetworkControllerCallback.class); + verify(mDataNetworkController).registerDataNetworkControllerCallback( + dataNetworkControllerCallbackCaptor.capture()); + DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue(); + callback.onNrAdvancedCapableByPcoChanged(true); processAllMessages(); assertEquals("connected_mmwave", getCurrentState().getName()); } @@ -502,7 +497,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateLegacy(); doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_DATA_RAT_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); } @@ -512,7 +507,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateNrConnected(); doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("restricted", getCurrentState().getName()); } @@ -523,9 +518,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateNrConnectedMmwave(); doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); - assertEquals("connected", getCurrentState().getName()); } @@ -535,9 +529,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateNrConnected(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); - assertEquals("connected_mmwave", getCurrentState().getName()); } @@ -545,12 +538,11 @@ public class NetworkTypeControllerTest extends TelephonyTest { public void testNrPhysicalChannelChangeFromNrConnectedMmwaveToLteConnected() throws Exception { testTransitionToCurrentStateNrConnectedMmwave(); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null)); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); - assertEquals("not_restricted_rrc_con", getCurrentState().getName()); } @@ -564,15 +556,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateNrConnectedMmwave(); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); setPhysicalLinkStatus(true); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_con", getCurrentState().getName()); } - @Test public void testUsingUserDataForRrcDetection_FromNrConnectedMmwaveToLteConnected() throws Exception { @@ -581,14 +571,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported( TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED); mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); processAllMessages(); testTransitionToCurrentStateNrConnectedMmwave(); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null)); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_con", getCurrentState().getName()); @@ -603,7 +592,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mServiceState).isUsingCarrierAggregation(); doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA, mNetworkTypeController.getOverrideNetworkType()); @@ -615,13 +605,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { mBundle = mContextFixture.getCarrierConfigBundle(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING, "connected_mmwave:5G_Plus,connected:5G"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); // Transition to LTE connected state doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null)); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_con", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, @@ -630,7 +620,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { // LTE -> LTE+ doReturn(true).when(mServiceState).isUsingCarrierAggregation(); doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA, mNetworkTypeController.getOverrideNetworkType()); @@ -642,13 +633,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { mBundle = mContextFixture.getCarrierConfigBundle(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING, "connected_mmwave:5G_Plus,connected:5G"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); // Transition to idle state doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null)); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_idle", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, @@ -657,7 +648,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { // LTE -> LTE+ doReturn(true).when(mServiceState).isUsingCarrierAggregation(); doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA, mNetworkTypeController.getOverrideNetworkType()); @@ -667,7 +659,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { public void testEventPhysicalLinkStatusChanged() throws Exception { testTransitionToCurrentStateLteConnected(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null)); processAllMessages(); @@ -684,8 +676,8 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateLteConnectedSupportPhysicalChannelConfig1_6(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); setPhysicalLinkStatus(false); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED); - + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_idle", getCurrentState().getName()); } @@ -698,11 +690,11 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported( TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED); mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); processAllMessages(); testTransitionToCurrentStateLteConnected_usingUserDataForRrcDetection(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null)); processAllMessages(); @@ -715,7 +707,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, + mNetworkTypeController.sendMessage(5 /* EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED */, new AsyncResult(null, false, null)); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, @@ -729,8 +721,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { mNetworkTypeController.getOverrideNetworkType()); doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - - mNetworkTypeController.sendMessage(EVENT_RADIO_OFF_OR_UNAVAILABLE); + mNetworkTypeController.sendMessage(9 /* EVENT_RADIO_OFF_OR_UNAVAILABLE */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, mNetworkTypeController.getOverrideNetworkType()); @@ -747,7 +738,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA)).when( mPhone).getCachedAllowedNetworkTypesBitmask(); - mNetworkTypeController.sendMessage(EVENT_PREFERRED_NETWORK_MODE_CHANGED); + mNetworkTypeController.sendMessage(10 /* EVENT_PREFERRED_NETWORK_MODE_CHANGED */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, mNetworkTypeController.getOverrideNetworkType()); @@ -758,7 +749,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, @@ -766,13 +757,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should trigger 10 second timer doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // timer expires moveTimeForward(10 * 1000); @@ -781,7 +772,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -789,14 +780,14 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); IPowerManager powerManager = mock(IPowerManager.class); PowerManager pm = new PowerManager(mContext, powerManager, mock(IThermalService.class), new Handler(Looper.myLooper())); doReturn(pm).when(mContext).getSystemService(Context.POWER_SERVICE); doReturn(true).when(powerManager).isDeviceIdleMode(); - mNetworkTypeController.sendMessage(17 /*EVENT_DEVICE_IDLE_MODE_CHANGED*/); + mNetworkTypeController.sendMessage(12 /* EVENT_DEVICE_IDLE_MODE_CHANGED */); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, @@ -804,13 +795,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should not trigger timer doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -818,7 +809,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, @@ -826,17 +817,17 @@ public class NetworkTypeControllerTest extends TelephonyTest { // trigger 10 second timer after disconnecting from NR doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // reconnect to NR in the middle of the timer doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); // timer expires moveTimeForward(10 * 1000); @@ -846,7 +837,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -854,7 +845,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, @@ -868,14 +859,14 @@ public class NetworkTypeControllerTest extends TelephonyTest { // trigger 10 second timer after disconnecting from NR, and then it does the timer reset // since the network mode without the NR capability. doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); // timer should be reset. assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -884,7 +875,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected_mmwave", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, @@ -892,13 +883,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should trigger 10 second timer doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // timer expires moveTimeForward(10 * 1000); @@ -907,7 +898,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -916,7 +907,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected_mmwave", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, @@ -924,17 +915,17 @@ public class NetworkTypeControllerTest extends TelephonyTest { // trigger 10 second timer after disconnecting from NR doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // reconnect to NR in the middle of the timer doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); // timer expires moveTimeForward(10 * 1000); @@ -944,7 +935,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected_mmwave", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -954,7 +945,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "connected,any,30"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, @@ -962,13 +953,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should trigger 10 second primary timer doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // primary timer expires moveTimeForward(10 * 1000); @@ -978,7 +969,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // secondary timer expires moveTimeForward(30 * 1000); @@ -987,7 +978,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -997,7 +988,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "connected,any,30"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, @@ -1005,13 +996,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should trigger 10 second primary timer doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // primary timer expires moveTimeForward(10 * 1000); @@ -1021,11 +1012,11 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // reconnect to NR in the middle of the timer doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); // secondary timer expires moveTimeForward(30 * 1000); @@ -1035,7 +1026,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -1046,7 +1037,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "connected_mmwave,any,30"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected_mmwave", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, @@ -1054,13 +1045,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should trigger 10 second primary timer doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // primary timer expires moveTimeForward(10 * 1000); @@ -1070,7 +1061,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // secondary timer expires moveTimeForward(30 * 1000); @@ -1079,7 +1070,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -1090,7 +1081,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "connected_mmwave,any,30"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected_mmwave", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, @@ -1098,13 +1089,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should trigger 10 second primary timer doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // primary timer expires moveTimeForward(10 * 1000); @@ -1114,11 +1105,11 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // reconnect to NR in the middle of the timer doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); // secondary timer expires moveTimeForward(30 * 1000); @@ -1128,7 +1119,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { assertEquals("connected_mmwave", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); } @Test @@ -1139,7 +1130,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "connected_mmwave,any,30"); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); assertEquals("connected_mmwave", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, @@ -1147,13 +1138,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { // should trigger 10 second primary timer doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange(); - mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, mNetworkTypeController.getOverrideNetworkType()); - assertTrue(mNetworkTypeController.is5GHysteresisActive()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); // rat is UMTS, should stop timer NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() @@ -1162,13 +1153,89 @@ public class NetworkTypeControllerTest extends TelephonyTest { .build(); doReturn(nri).when(mServiceState).getNetworkRegistrationInfo(anyInt(), anyInt()); doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); - mNetworkTypeController.sendMessage(EVENT_DATA_RAT_CHANGED); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("legacy", getCurrentState().getName()); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, mNetworkTypeController.getOverrideNetworkType()); - assertFalse(mNetworkTypeController.is5GHysteresisActive()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); + } + + @Test + public void testNrTimerResetWhenConnected() throws Exception { + mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, + "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); + mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, + "connected_mmwave,any,30"); + sendCarrierConfigChanged(); + + doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); + mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */, + new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null)); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); + processAllMessages(); + + assertEquals("not_restricted_rrc_con", getCurrentState().getName()); + assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, + mNetworkTypeController.getOverrideNetworkType()); + + // should trigger 10 second primary timer + doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); + doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange(); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); + processAllMessages(); + + assertEquals("legacy", getCurrentState().getName()); + assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA, + mNetworkTypeController.getOverrideNetworkType()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); + + // rat is NR, should stop timer + NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR) + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) + .build(); + doReturn(nri).when(mServiceState).getNetworkRegistrationInfo(anyInt(), anyInt()); + doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); + processAllMessages(); + + assertEquals("connected", getCurrentState().getName()); + assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, + mNetworkTypeController.getOverrideNetworkType()); + assertFalse(mNetworkTypeController.areAnyTimersActive()); + } + + @Test + public void testNrTimerResetWhenConnectedAdvanced() throws Exception { + testTransitionToCurrentStateNrConnectedMmwave(); + mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, + "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10"); + mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, + "connected_mmwave,any,30"); + sendCarrierConfigChanged(); + + // should trigger 10 second primary timer + doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState(); + doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange(); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); + processAllMessages(); + + assertEquals("legacy", getCurrentState().getName()); + assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, + mNetworkTypeController.getOverrideNetworkType()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); + + // not advanced, should not stop timer + doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); + processAllMessages(); + + assertEquals("connected", getCurrentState().getName()); + assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, + mNetworkTypeController.getOverrideNetworkType()); + assertTrue(mNetworkTypeController.areAnyTimersActive()); } private void setPhysicalLinkStatus(boolean state) { @@ -1195,9 +1262,9 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); doReturn(new int[] {19999}).when(mServiceState).getCellBandwidths(); mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); } @@ -1214,7 +1281,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { .build()); doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); processAllMessages(); @@ -1241,7 +1308,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { mBundle.putBoolean( CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, true); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); processAllMessages(); @@ -1255,9 +1322,9 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); mBundle.putBoolean(CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, false); - broadcastCarrierConfigs(); + sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); } diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java index bac1adbc86ea12db1bb256b09fa3473eeefcc22f..700a246815e016b246385101332b6018063adc4e 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java @@ -136,6 +136,27 @@ public class PhoneConfigurationManagerTest extends TelephonyTest { assertEquals(PhoneCapability.DEFAULT_DSDS_CAPABILITY, mPcm.getStaticPhoneCapability()); } + @Test + @SmallTest + public void testConfigureAndGetMaxActiveVoiceSubscriptions() throws Exception { + init(2); + assertEquals(1, mPcm.getStaticPhoneCapability().getMaxActiveVoiceSubscriptions()); + + PhoneCapability dualActiveVoiceSubCapability = new PhoneCapability.Builder( + PhoneCapability.DEFAULT_DSDS_CAPABILITY) + .setMaxActiveVoiceSubscriptions(2) + .build(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); + verify(mMockRadioConfig).getPhoneCapability(captor.capture()); + Message msg = captor.getValue(); + AsyncResult.forMessage(msg, dualActiveVoiceSubCapability, null); + msg.sendToTarget(); + processAllMessages(); + + assertEquals(2, mPcm.getStaticPhoneCapability().getMaxActiveVoiceSubscriptions()); + } + @Test @SmallTest public void testSwitchMultiSimConfig_notDsdsCapable_shouldFail() throws Exception { @@ -199,17 +220,9 @@ public class PhoneConfigurationManagerTest extends TelephonyTest { // Verify clearSubInfoRecord() and onSlotActiveStatusChange() are called for second phone, // and not for the first one - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService).markSubscriptionsInactive(1); - } else { - verify(mSubscriptionController).clearSubInfoRecord(1); - } + verify(mSubscriptionManagerService).markSubscriptionsInactive(1); verify(mMockCi1).onSlotActiveStatusChange(anyBoolean()); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, never()).markSubscriptionsInactive(0); - } else { - verify(mSubscriptionController, never()).clearSubInfoRecord(0); - } + verify(mSubscriptionManagerService, never()).markSubscriptionsInactive(0); verify(mMockCi0, never()).onSlotActiveStatusChange(anyBoolean()); // Verify onPhoneRemoved() gets called on MultiSimSettingController phone @@ -238,12 +251,6 @@ public class PhoneConfigurationManagerTest extends TelephonyTest { */ // setup mocks for VOICE mSubscriptionManagerService. getter/setter - doAnswer(invocation -> { - Integer value = (Integer) invocation.getArguments()[0]; - Mockito.when(mSubscriptionController.getDefaultVoiceSubId()).thenReturn(value); - return null; - }).when(mSubscriptionController).setDefaultVoiceSubId(anyInt()); - doAnswer(invocation -> { Integer value = (Integer) invocation.getArguments()[0]; Mockito.when(mSubscriptionManagerService.getDefaultVoiceSubId()).thenReturn(value); @@ -254,30 +261,17 @@ public class PhoneConfigurationManagerTest extends TelephonyTest { // start off the phone stat with 1 active sim. reset values for new test. init(1); - if (isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.setDefaultVoiceSubId(startingDefaultSubscriptionId); - assertEquals(startingDefaultSubscriptionId, - mSubscriptionManagerService.getDefaultVoiceSubId()); - } else { - mSubscriptionController.setDefaultVoiceSubId(startingDefaultSubscriptionId); - assertEquals(startingDefaultSubscriptionId, - mSubscriptionController.getDefaultVoiceSubId()); - } + mSubscriptionManagerService.setDefaultVoiceSubId(startingDefaultSubscriptionId); + assertEquals(startingDefaultSubscriptionId, + mSubscriptionManagerService.getDefaultVoiceSubId()); // Perform the switch to DSDS mode and ensure all existing checks are not altered testSwitchFromSingleToDualSimModeNoReboot(); // VOICE check - if (isSubscriptionManagerServiceEnabled()) { - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID /* No CALL Preference value */, - mSubscriptionManagerService.getDefaultVoiceSubId()); - // Now, when the user goes to place a CALL, they will be prompted on which sim to use. - } else { - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID /* No CALL Preference value */, - mSubscriptionController.getDefaultVoiceSubId()); - // Now, when the user goes to place a CALL, they will be prompted on which sim to use. - } - + assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID /* No CALL Preference value */, + mSubscriptionManagerService.getDefaultVoiceSubId()); + // Now, when the user goes to place a CALL, they will be prompted on which sim to use. } /** diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java deleted file mode 100644 index d1aea0334cfc47d6de5b17a0f28ffc7fae4da19e..0000000000000000000000000000000000000000 --- a/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import static org.junit.Assert.fail; - -import android.test.suitebuilder.annotation.SmallTest; - -import org.junit.Test; - -public class PhoneFactoryTest { - @Test - @SmallTest - public void testBeforeMakePhone() { - try { - PhoneFactory.getDefaultPhone(); - fail("Expecting IllegalStateException"); - } catch (IllegalStateException e) { - } - - try { - PhoneFactory.getPhone(0); - fail("Expecting IllegalStateException"); - } catch (IllegalStateException e) { - } - - try { - PhoneFactory.getPhones(); - fail("Expecting IllegalStateException"); - } catch (IllegalStateException e) { - } - - try { - PhoneFactory.getNetworkFactory(0); - fail("Expecting IllegalStateException"); - } catch (IllegalStateException e) { - } - } - - //todo: add test for makeDefaultPhone(). will need some refactoring in PhoneFactory. -} diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java index b7d2913ec542f527faa5e4e5620c95bfa189dff1..2f3bbf713f304dac5a8d5b845c43747aec274ec2 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java @@ -562,6 +562,17 @@ public class PhoneNumberUtilsTest { assertEquals("800-GOOG-114", PhoneNumberUtils.formatNumber("800-GOOG-114", "US")); } + @Test + public void testFormatNumber_lowerCase() { + assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "us")); + assertEquals("223-4567", PhoneNumberUtils.formatNumber("2234567", "us")); + assertEquals("011 86 10 8888 0000", + PhoneNumberUtils.formatNumber("011861088880000", "us")); + assertEquals("010 8888 0000", PhoneNumberUtils.formatNumber("01088880000", "cn")); + // formatNumber doesn't format alpha numbers, but keep them as they are. + assertEquals("800-GOOG-114", PhoneNumberUtils.formatNumber("800-GOOG-114", "us")); + } + /** * Tests ability to format phone numbers from Japan using the international format when the * current country is not Japan. @@ -570,6 +581,9 @@ public class PhoneNumberUtilsTest { @Test public void testFormatJapanInternational() { assertEquals("+81 90-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "US")); + + // Again with lower case country code + assertEquals("+81 90-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "us")); } /** @@ -582,8 +596,15 @@ public class PhoneNumberUtilsTest { assertEquals("090-6657-0660", PhoneNumberUtils.formatNumber("09066570660", "JP")); assertEquals("090-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "JP")); + // Again with lower case country code + assertEquals("090-6657-0660", PhoneNumberUtils.formatNumber("09066570660", "jp")); + assertEquals("090-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "jp")); + + // US number should still be internationally formatted assertEquals("+1 650-555-1212", PhoneNumberUtils.formatNumber("+16505551212", "JP")); + // Again with lower case country code + assertEquals("+1 650-555-1212", PhoneNumberUtils.formatNumber("+16505551212", "jp")); } @SmallTest @@ -600,6 +621,20 @@ public class PhoneNumberUtilsTest { assertEquals("**650 2910000", PhoneNumberUtils.formatNumber("**650 2910000", "US")); } + // Same as testFormatNumber_LeadingStarAndHash but using lower case country code. + @Test + public void testFormatNumber_LeadingStarAndHash_countryCodeLowerCase() { + // Numbers with a leading '*' or '#' should be left unchanged. + assertEquals("*650 2910000", PhoneNumberUtils.formatNumber("*650 2910000", "us")); + assertEquals("#650 2910000", PhoneNumberUtils.formatNumber("#650 2910000", "us")); + assertEquals("*#650 2910000", PhoneNumberUtils.formatNumber("*#650 2910000", "us")); + assertEquals("#*650 2910000", PhoneNumberUtils.formatNumber("#*650 2910000", "us")); + assertEquals("#650*2910000", PhoneNumberUtils.formatNumber("#650*2910000", "us")); + assertEquals("#650*2910000", PhoneNumberUtils.formatNumber("#650*2910000", "us")); + assertEquals("##650 2910000", PhoneNumberUtils.formatNumber("##650 2910000", "us")); + assertEquals("**650 2910000", PhoneNumberUtils.formatNumber("**650 2910000", "us")); + } + @SmallTest @Test public void testNormalizeNumber() { @@ -641,59 +676,63 @@ public class PhoneNumberUtilsTest { PhoneNumberUtils.formatNumber("011861088880000", "", "GB")); } + // Same as testFormatDailabeNumber but using lower case country code. + @Test + public void testFormatDailabeNumber_countryCodeLowerCase() { + // Using the phoneNumberE164's country code + assertEquals("(650) 291-0000", + PhoneNumberUtils.formatNumber("6502910000", "+16502910000", "cn")); + // Using the default country code for a phone number containing the IDD + assertEquals("011 86 10 8888 0000", + PhoneNumberUtils.formatNumber("011861088880000", "+861088880000", "us")); + assertEquals("00 86 10 8888 0000", + PhoneNumberUtils.formatNumber("00861088880000", "+861088880000", "gb")); + assertEquals("+86 10 8888 0000", + PhoneNumberUtils.formatNumber("+861088880000", "+861088880000", "gb")); + // Wrong default country, so no formatting is done + assertEquals("011861088880000", + PhoneNumberUtils.formatNumber("011861088880000", "+861088880000", "gb")); + // The phoneNumberE164 is null + assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("6502910000", null, "us")); + // The given number has a country code. + assertEquals("+1 650-291-0000", PhoneNumberUtils.formatNumber("+16502910000", null, "cn")); + // The given number was formatted. + assertEquals("650-291-0000", PhoneNumberUtils.formatNumber("650-291-0000", null, "us")); + // A valid Polish number should be formatted. + assertEquals("506 128 687", PhoneNumberUtils.formatNumber("506128687", null, "pl")); + // An invalid Polish number should be left as it is. Note Poland doesn't use '0' as a + // national prefix; therefore, the leading '0' makes the number invalid. + assertEquals("0506128687", PhoneNumberUtils.formatNumber("0506128687", null, "pl")); + // Wrong default country, so no formatting is done + assertEquals("011861088880000", + PhoneNumberUtils.formatNumber("011861088880000", "", "gb")); + } + @FlakyTest @Test @Ignore public void testIsEmergencyNumber() { - // There are two parallel sets of tests here: one for the - // regular isEmergencyNumber() method, and the other for - // isPotentialEmergencyNumber(). - // // (The difference is that isEmergencyNumber() will return true // only if the specified number exactly matches an actual - // emergency number, but isPotentialEmergencyNumber() will - // return true if the specified number simply starts with the - // same digits as any actual emergency number.) + // emergency number // Tests for isEmergencyNumber(): - assertTrue(PhoneNumberUtils.isEmergencyNumber("911", "US")); - assertTrue(PhoneNumberUtils.isEmergencyNumber("112", "US")); + assertTrue(PhoneNumberUtils.isEmergencyNumber("911")); + assertTrue(PhoneNumberUtils.isEmergencyNumber("112")); // The next two numbers are not valid phone numbers in the US, // so do not count as emergency numbers (but they *are* "potential" // emergency numbers; see below.) - assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345", "US")); - assertFalse(PhoneNumberUtils.isEmergencyNumber("11212345", "US")); + assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345")); + assertFalse(PhoneNumberUtils.isEmergencyNumber("11212345")); // A valid mobile phone number from Singapore shouldn't be classified as an emergency number // in Singapore, as 911 is not an emergency number there. - assertFalse(PhoneNumberUtils.isEmergencyNumber("91121234", "SG")); + assertFalse(PhoneNumberUtils.isEmergencyNumber("91121234")); // A valid fixed-line phone number from Brazil shouldn't be classified as an emergency number // in Brazil, as 112 is not an emergency number there. - assertFalse(PhoneNumberUtils.isEmergencyNumber("1121234567", "BR")); + assertFalse(PhoneNumberUtils.isEmergencyNumber("1121234567")); // A valid local phone number from Brazil shouldn't be classified as an emergency number in // Brazil. - assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345", "BR")); - - // Tests for isPotentialEmergencyNumber(): - // These first two are obviously emergency numbers: - assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("911", "US")); - assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("112", "US")); - // The next two numbers are not valid phone numbers in the US, but can be used to trick the - // system to dial 911 and 112, which are emergency numbers in the US. For the purpose of - // addressing that, they are also classified as "potential" emergency numbers in the US. - assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "US")); - assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("11212345", "US")); - - // A valid mobile phone number from Singapore shouldn't be classified as an emergency number - // in Singapore, as 911 is not an emergency number there. - // This test fails on devices that have ecclist property preloaded with 911. - // assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91121234", "SG")); - - // A valid fixed-line phone number from Brazil shouldn't be classified as an emergency number - // in Brazil, as 112 is not an emergency number there. - assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("1121234567", "BR")); - // A valid local phone number from Brazil shouldn't be classified as an emergency number in - // Brazil. - assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "BR")); + assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345")); } @SmallTest @@ -768,6 +807,22 @@ public class PhoneNumberUtilsTest { assertFalse(PhoneNumberUtils.isInternationalNumber("011-613-966-94916", "AU")); } + // Same as testIsInternational but using lower case country code. + @Test + public void testIsInternational_countryCodeLowerCase() { + assertFalse(PhoneNumberUtils.isInternationalNumber("", "us")); + assertFalse(PhoneNumberUtils.isInternationalNumber(null, "us")); + assertFalse(PhoneNumberUtils.isInternationalNumber("+16505551212", "us")); + assertTrue(PhoneNumberUtils.isInternationalNumber("+16505551212", "uk")); + assertTrue(PhoneNumberUtils.isInternationalNumber("+16505551212", "jp")); + assertTrue(PhoneNumberUtils.isInternationalNumber("+86 10 8888 0000", "us")); + assertTrue(PhoneNumberUtils.isInternationalNumber("001-541-754-3010", "de")); + assertFalse(PhoneNumberUtils.isInternationalNumber("001-541-754-3010", "us")); + assertTrue(PhoneNumberUtils.isInternationalNumber("01161396694916", "us")); + assertTrue(PhoneNumberUtils.isInternationalNumber("011-613-966-94916", "us")); + assertFalse(PhoneNumberUtils.isInternationalNumber("011-613-966-94916", "au")); + } + @SmallTest @Test public void testIsUriNumber() { diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java index b8fc6091e597793ee6046ecb4ae2aa758d25b971..00634a05da153efd544fc3236c43c2732f351429 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java @@ -16,13 +16,17 @@ package com.android.internal.telephony; import static android.Manifest.permission.READ_PHONE_STATE; +import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; +import static android.telephony.TelephonyManager.APPTYPE_ISIM; +import static android.telephony.TelephonyManager.APPTYPE_USIM; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -31,17 +35,32 @@ import android.app.AppOpsManager; import android.app.PropertyInvalidatedCache; import android.content.Context; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.RemoteException; import android.test.suitebuilder.annotation.SmallTest; +import com.android.internal.telephony.uicc.IsimUiccRecords; +import com.android.internal.telephony.uicc.SIMRecords; +import com.android.internal.telephony.uicc.UiccCardApplication; +import com.android.internal.telephony.uicc.UiccPort; +import com.android.internal.telephony.uicc.UiccProfile; + import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; + +import java.util.List; public class PhoneSubInfoControllerTest extends TelephonyTest { private static final String FEATURE_ID = "myfeatureId"; + private static final String PSI_SMSC_TEL1 = "tel:+91123456789"; + private static final String PSI_SMSC_SIP1 = "sip:+1234567890@abc.pc.operetor1.com;user=phone"; + private static final String PSI_SMSC_TEL2 = "tel:+91987654321"; + private static final String PSI_SMSC_SIP2 = "sip:+19876543210@dcf.pc.operetor2.com;user=phone"; private PhoneSubInfoController mPhoneSubInfoControllerUT; private AppOpsManager mAppOsMgr; @@ -57,22 +76,15 @@ public class PhoneSubInfoControllerTest extends TelephonyTest { PropertyInvalidatedCache.disableForTestMode(); /* mPhone -> PhoneId: 0 -> SubId:0 mSecondPhone -> PhoneId:1 -> SubId: 1*/ - doReturn(0).when(mSubscriptionController).getPhoneId(eq(0)); doReturn(0).when(mSubscriptionManagerService).getPhoneId(eq(0)); - doReturn(1).when(mSubscriptionController).getPhoneId(eq(1)); doReturn(1).when(mSubscriptionManagerService).getPhoneId(eq(1)); doReturn(2).when(mTelephonyManager).getPhoneCount(); doReturn(2).when(mTelephonyManager).getActiveModemCount(); - doReturn(true).when(mSubscriptionController).isActiveSubId(0, TAG, FEATURE_ID); doReturn(true).when(mSubscriptionManagerService).isActiveSubId(0, TAG, FEATURE_ID); - doReturn(true).when(mSubscriptionController).isActiveSubId(1, TAG, FEATURE_ID); doReturn(true).when(mSubscriptionManagerService).isActiveSubId(1, TAG, FEATURE_ID); doReturn(new int[]{0, 1}).when(mSubscriptionManager) .getCompleteActiveSubscriptionIdList(); - mServiceManagerMockedServices.put("isub", mSubscriptionController); - doReturn(mSubscriptionController).when(mSubscriptionController) - .queryLocalInterface(anyString()); doReturn(mContext).when(mSecondPhone).getContext(); mAppOsMgr = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); @@ -926,4 +938,357 @@ public class PhoneSubInfoControllerTest extends TelephonyTest { assertEquals("VM_SIM_1", mPhoneSubInfoControllerUT .getVoiceMailAlphaTagForSubscriber(1, TAG, FEATURE_ID)); } -} + + private void setUpInitials() { + UiccPort uiccPort1 = Mockito.mock(UiccPort.class); + UiccProfile uiccProfile1 = Mockito.mock(UiccProfile.class); + UiccCardApplication uiccCardApplication1 = Mockito.mock(UiccCardApplication.class); + SIMRecords simRecords1 = Mockito.mock(SIMRecords.class); + IsimUiccRecords isimUiccRecords1 = Mockito.mock(IsimUiccRecords.class); + + doReturn(uiccPort1).when(mPhone).getUiccPort(); + doReturn(uiccProfile1).when(uiccPort1).getUiccProfile(); + doReturn(uiccCardApplication1).when(uiccProfile1).getApplicationByType(anyInt()); + doReturn(simRecords1).when(uiccCardApplication1).getIccRecords(); + doReturn(isimUiccRecords1).when(uiccCardApplication1).getIccRecords(); + doReturn(PSI_SMSC_TEL1).when(simRecords1).getSmscIdentity(); + doReturn(PSI_SMSC_TEL1).when(isimUiccRecords1).getSmscIdentity(); + + doReturn(mUiccPort).when(mSecondPhone).getUiccPort(); + doReturn(mUiccProfile).when(mUiccPort).getUiccProfile(); + doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + doReturn(mIsimUiccRecords).when(mUiccCardApplicationIms).getIccRecords(); + doReturn(PSI_SMSC_TEL2).when(mSimRecords).getSmscIdentity(); + doReturn(PSI_SMSC_TEL2).when(mIsimUiccRecords).getSmscIdentity(); + } + + @Test + public void testGetSmscIdentityForTelUri() { + try { + setUpInitials(); + assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT + .getSmscIdentity(0, APPTYPE_ISIM).toString()); + assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT + .getSmscIdentity(0, APPTYPE_USIM).toString()); + assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT + .getSmscIdentity(1, APPTYPE_ISIM).toString()); + assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT + .getSmscIdentity(1, APPTYPE_USIM).toString()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetSmscIdentityForSipUri() { + try { + UiccPort uiccPort1 = Mockito.mock(UiccPort.class); + UiccProfile uiccProfile1 = Mockito.mock(UiccProfile.class); + UiccCardApplication uiccCardApplication1 = Mockito.mock(UiccCardApplication.class); + SIMRecords simRecords1 = Mockito.mock(SIMRecords.class); + IsimUiccRecords isimUiccRecords1 = Mockito.mock(IsimUiccRecords.class); + + doReturn(uiccPort1).when(mPhone).getUiccPort(); + doReturn(uiccProfile1).when(uiccPort1).getUiccProfile(); + doReturn(uiccCardApplication1).when(uiccProfile1).getApplicationByType(anyInt()); + doReturn(simRecords1).when(uiccCardApplication1).getIccRecords(); + doReturn(isimUiccRecords1).when(uiccCardApplication1).getIccRecords(); + doReturn(PSI_SMSC_SIP1).when(simRecords1).getSmscIdentity(); + doReturn(PSI_SMSC_SIP1).when(isimUiccRecords1).getSmscIdentity(); + + doReturn(mUiccPort).when(mSecondPhone).getUiccPort(); + doReturn(mUiccProfile).when(mUiccPort).getUiccProfile(); + doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + doReturn(mIsimUiccRecords).when(mUiccCardApplicationIms).getIccRecords(); + doReturn(PSI_SMSC_SIP2).when(mSimRecords).getSmscIdentity(); + doReturn(PSI_SMSC_SIP2).when(mIsimUiccRecords).getSmscIdentity(); + + assertEquals(PSI_SMSC_SIP1, mPhoneSubInfoControllerUT + .getSmscIdentity(0, APPTYPE_ISIM).toString()); + assertEquals(PSI_SMSC_SIP1, mPhoneSubInfoControllerUT + .getSmscIdentity(0, APPTYPE_USIM).toString()); + assertEquals(PSI_SMSC_SIP2, mPhoneSubInfoControllerUT + .getSmscIdentity(1, APPTYPE_ISIM).toString()); + assertEquals(PSI_SMSC_SIP2, mPhoneSubInfoControllerUT + .getSmscIdentity(1, APPTYPE_USIM).toString()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetSmscIdentityWithOutPermissions() { + setUpInitials(); + + //case 1: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION + mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); + try { + mPhoneSubInfoControllerUT.getSmscIdentity(0, APPTYPE_ISIM); + Assert.fail("expected Security Exception Thrown"); + } catch (Exception ex) { + assertTrue(ex instanceof SecurityException); + assertTrue(ex.getMessage().contains("getSmscIdentity")); + } + + try { + mPhoneSubInfoControllerUT.getSmscIdentity(1, APPTYPE_ISIM); + Assert.fail("expected Security Exception Thrown"); + } catch (Exception ex) { + assertTrue(ex instanceof SecurityException); + assertTrue(ex.getMessage().contains("getSmscIdentity")); + } + + try { + mPhoneSubInfoControllerUT.getSmscIdentity(0, APPTYPE_USIM); + Assert.fail("expected Security Exception Thrown"); + } catch (Exception ex) { + assertTrue(ex instanceof SecurityException); + assertTrue(ex.getMessage().contains("getSmscIdentity")); + } + + try { + mPhoneSubInfoControllerUT.getSmscIdentity(1, APPTYPE_USIM); + Assert.fail("expected Security Exception Thrown"); + } catch (Exception ex) { + assertTrue(ex instanceof SecurityException); + assertTrue(ex.getMessage().contains("getSmscIdentity")); + } + + //case 2: no READ_PRIVILEGED_PHONE_STATE + mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE); + doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp( + eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID), + nullable(String.class)); + + try { + assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT + .getSmscIdentity(0, APPTYPE_ISIM).toString()); + assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT + .getSmscIdentity(0, APPTYPE_USIM).toString()); + assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT + .getSmscIdentity(1, APPTYPE_ISIM).toString()); + assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT + .getSmscIdentity(1, APPTYPE_USIM).toString()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetSimServiceTable() throws RemoteException { + String refSst = "1234567"; + doReturn(mUiccPort).when(mPhone).getUiccPort(); + doReturn(mUiccProfile).when(mUiccPort).getUiccProfile(); + doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + + doReturn(refSst).when(mSimRecords).getSimServiceTable(); + + String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()); + assertEquals(refSst, resultSst); + } + + @Test + public void testGetSimServiceTableEmpty() throws RemoteException { + String refSst = null; + doReturn(mUiccPort).when(mPhone).getUiccPort(); + doReturn(mUiccProfile).when(mUiccPort).getUiccProfile(); + doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + + doReturn(refSst).when(mSimRecords).getSimServiceTable(); + + String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()); + assertEquals(refSst, resultSst); + } + + @Test + public void testGetSstWhenNoUiccPort() throws RemoteException { + String refSst = "1234567"; + doReturn(null).when(mPhone).getUiccPort(); + doReturn(mUiccProfile).when(mUiccPort).getUiccProfile(); + doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + + doReturn(refSst).when(mSimRecords).getSimServiceTable(); + + String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()); + assertEquals(null, resultSst); + } + + @Test + public void testGetSstWhenNoUiccProfile() throws RemoteException { + String refSst = "1234567"; + doReturn(mUiccPort).when(mPhone).getUiccPort(); + doReturn(null).when(mUiccPort).getUiccProfile(); + doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + + doReturn(refSst).when(mSimRecords).getSimServiceTable(); + + String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()); + assertEquals(null, resultSst); + } + + @Test + public void testGetSstWhenNoUiccApplication() throws RemoteException { + String refSst = "1234567"; + doReturn(mUiccPort).when(mPhone).getUiccPort(); + doReturn(mUiccProfile).when(mUiccPort).getUiccProfile(); + doReturn(null).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + + doReturn(refSst).when(mSimRecords).getSimServiceTable(); + + String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()); + assertEquals(null, resultSst); + } + + @Test + public void testGetSimServiceTableWithOutPermissions() throws RemoteException { + String refSst = "1234567"; + doReturn(mUiccPort).when(mPhone).getUiccPort(); + doReturn(mUiccProfile).when(mUiccPort).getUiccProfile(); + doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt()); + doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords(); + + doReturn(refSst).when(mSimRecords).getSimServiceTable(); + + mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); + try { + mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()); + Assert.fail("expected Security Exception Thrown"); + } catch (Exception ex) { + assertTrue(ex instanceof SecurityException); + assertTrue(ex.getMessage().contains("getSimServiceTable")); + } + + mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE); + assertEquals(refSst, mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt())); + } + + @Test + public void getPrivateUserIdentity() { + String refImpi = "1234567890@example.com"; + doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords(); + doReturn(refImpi).when(mIsimUiccRecords).getIsimImpi(); + + doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow( + eq(AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER), anyInt(), eq(TAG), + eq(FEATURE_ID), nullable(String.class)); + + String impi = mPhoneSubInfoControllerUT.getImsPrivateUserIdentity(0, TAG, FEATURE_ID); + assertEquals(refImpi, impi); + } + + @Test + public void getPrivateUserIdentity_NoPermission() { + String refImpi = "1234567890@example.com"; + doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords(); + doReturn(refImpi).when(mIsimUiccRecords).getIsimImpi(); + + try { + mPhoneSubInfoControllerUT.getImsPrivateUserIdentity(0, TAG, FEATURE_ID); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof SecurityException); + assertTrue(ex.getMessage().contains("No permissions to the caller")); + } + } + + @Test + public void getPrivateUserIdentity_InValidSubIdCheck() { + String refImpi = "1234567890@example.com"; + doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords(); + doReturn(refImpi).when(mIsimUiccRecords).getIsimImpi(); + + try { + mPhoneSubInfoControllerUT.getImsPrivateUserIdentity(-1, TAG, FEATURE_ID); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof IllegalArgumentException); + assertTrue(ex.getMessage().contains("Invalid SubscriptionID")); + } + } + + @Test + public void getImsPublicUserIdentities() { + String[] refImpuArray = new String[3]; + refImpuArray[0] = "012345678"; + refImpuArray[1] = "sip:test@verify.com"; + refImpuArray[2] = "tel:+91987754324"; + doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords(); + doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu(); + + List impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG, + FEATURE_ID); + + assertNotNull(impuList); + assertEquals(refImpuArray.length, impuList.size()); + assertEquals(impuList.get(0).toString(), refImpuArray[0]); + assertEquals(impuList.get(1).toString(), refImpuArray[1]); + assertEquals(impuList.get(2).toString(), refImpuArray[2]); + } + + @Test + public void getImsPublicUserIdentities_InvalidImpu() { + String[] refImpuArray = new String[3]; + refImpuArray[0] = null; + refImpuArray[2] = ""; + refImpuArray[2] = "tel:+91987754324"; + doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords(); + doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu(); + List impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG, + FEATURE_ID); + assertNotNull(impuList); + // Null or Empty string cannot be converted to URI + assertEquals(refImpuArray.length - 2, impuList.size()); + } + + @Test + public void getImsPublicUserIdentities_IsimNotLoadedError() { + doReturn(null).when(mPhone).getIsimRecords(); + + try { + mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG, FEATURE_ID); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof IllegalStateException); + assertTrue(ex.getMessage().contains("ISIM is not loaded")); + } + } + + @Test + public void getImsPublicUserIdentities_InValidSubIdCheck() { + try { + mPhoneSubInfoControllerUT.getImsPublicUserIdentities(-1, TAG, FEATURE_ID); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof IllegalArgumentException); + assertTrue(ex.getMessage().contains("Invalid SubscriptionID")); + } + } + + @Test + public void getImsPublicUserIdentities_NoReadPrivilegedPermission() { + mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); + String[] refImpuArray = new String[3]; + refImpuArray[0] = "012345678"; + refImpuArray[1] = "sip:test@verify.com"; + refImpuArray[2] = "tel:+91987754324"; + doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords(); + doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu(); + + List impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG, + FEATURE_ID); + + assertNotNull(impuList); + assertEquals(refImpuArray.length, impuList.size()); + assertEquals(impuList.get(0).toString(), refImpuArray[0]); + assertEquals(impuList.get(1).toString(), refImpuArray[1]); + assertEquals(impuList.get(2).toString(), refImpuArray[2]); + mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE); + } +} \ No newline at end of file diff --git a/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java index c0d497d98b546d105741c86e0879e5724260d81a..65ab66494d66975ec9e6135fb16cfc80de319ffb 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java @@ -19,10 +19,13 @@ package com.android.internal.telephony; import static android.telephony.RadioAccessFamily.RAF_GSM; import static android.telephony.RadioAccessFamily.RAF_LTE; +import static com.android.internal.telephony.ProxyController.EVENT_FINISH_RC_RESPONSE; import static com.android.internal.telephony.ProxyController.EVENT_MULTI_SIM_CONFIG_CHANGED; import static com.android.internal.telephony.ProxyController.EVENT_START_RC_RESPONSE; +import static com.android.internal.telephony.ProxyController.EVENT_TIMEOUT; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -97,12 +100,139 @@ public class ProxyControllerTest extends TelephonyTest { rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE); mProxyController.setRadioCapability(rafs); - Message.obtain(mProxyController.mHandler, EVENT_START_RC_RESPONSE, - new AsyncResult(null, null, - new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED))) + Message.obtain( + mProxyController.mHandler, + EVENT_START_RC_RESPONSE, + new AsyncResult( + null, + null, + new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED))) .sendToTarget(); processAllMessages(); assertFalse(mProxyController.isWakeLockHeld()); } + + @Test + @SmallTest + public void testWithNonPermanentExceptionOnRCResponse_WithExceptionOnFinishResponse() + throws Exception { + int activeModemCount = 2; + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2}); + doReturn(activeModemCount).when(mTelephonyManager).getPhoneCount(); + doReturn(RAF_GSM | RAF_LTE).when(mPhone).getRadioAccessFamily(); + doReturn(RAF_GSM).when(mPhone2).getRadioAccessFamily(); + + Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget(); + processAllMessages(); + verify(mPhone2).registerForRadioCapabilityChanged(any(), anyInt(), any()); + + RadioAccessFamily[] rafs = new RadioAccessFamily[activeModemCount]; + rafs[0] = new RadioAccessFamily(0, RAF_GSM); + rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE); + mProxyController.setRadioCapability(rafs); + + Message.obtain( + mProxyController.mHandler, + EVENT_START_RC_RESPONSE, + new AsyncResult( + null, + null, + new CommandException(CommandException.Error.RADIO_NOT_AVAILABLE))) + .sendToTarget(); + processAllMessages(); + assertTrue(mProxyController.isWakeLockHeld()); + onFinishResponseWithException(); + } + + @Test + @SmallTest + public void testWithNonPermanentExceptionOnRCResponse_WithoutExceptionOnFinishResponse() + throws Exception { + int activeModemCount = 2; + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2}); + doReturn(activeModemCount).when(mTelephonyManager).getPhoneCount(); + doReturn(RAF_GSM | RAF_LTE).when(mPhone).getRadioAccessFamily(); + doReturn(RAF_GSM).when(mPhone2).getRadioAccessFamily(); + + Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget(); + processAllMessages(); + verify(mPhone2).registerForRadioCapabilityChanged(any(), anyInt(), any()); + + RadioAccessFamily[] rafs = new RadioAccessFamily[activeModemCount]; + rafs[0] = new RadioAccessFamily(0, RAF_GSM); + rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE); + mProxyController.setRadioCapability(rafs); + + Message.obtain( + mProxyController.mHandler, + EVENT_START_RC_RESPONSE, + new AsyncResult(null, null, null)) + .sendToTarget(); + processAllMessages(); + assertTrue(mProxyController.isWakeLockHeld()); + onFinishResponseWithoutException(); + } + + @Test + @SmallTest + public void testOnRCResponseTimeout_WithExceptionOnFinishResponse() throws Exception { + int activeModemCount = 2; + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2}); + doReturn(activeModemCount).when(mTelephonyManager).getPhoneCount(); + doReturn(RAF_GSM | RAF_LTE).when(mPhone).getRadioAccessFamily(); + doReturn(RAF_GSM).when(mPhone2).getRadioAccessFamily(); + + Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget(); + processAllMessages(); + verify(mPhone2).registerForRadioCapabilityChanged(any(), anyInt(), any()); + + RadioAccessFamily[] rafs = new RadioAccessFamily[activeModemCount]; + rafs[0] = new RadioAccessFamily(0, RAF_GSM); + rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE); + mProxyController.setRadioCapability(rafs); + + Message.obtain( + mProxyController.mHandler, + EVENT_TIMEOUT, + new AsyncResult( + null, + null, + new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED))) + .sendToTarget(); + processAllMessages(); + onFinishResponseWithException(); + } + + private void onFinishResponseWithException() throws Exception { + replaceInstance( + ProxyController.class, "mRadioAccessFamilyStatusCounter", mProxyController, 1); + replaceInstance(ProxyController.class, "mTransactionFailed", mProxyController, true); + Message.obtain( + mProxyController.mHandler, + EVENT_FINISH_RC_RESPONSE, + new AsyncResult( + null, + null, + new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED))) + .sendToTarget(); + processAllMessages(); + assertTrue(mProxyController.isWakeLockHeld()); + } + + private void onFinishResponseWithoutException() throws Exception { + replaceInstance( + ProxyController.class, "mRadioAccessFamilyStatusCounter", mProxyController, 1); + replaceInstance(ProxyController.class, "mTransactionFailed", mProxyController, false); + replaceInstance( + ProxyController.class, "mRadioCapabilitySessionId", mProxyController, 123456); + Message.obtain( + mProxyController.mHandler, + EVENT_FINISH_RC_RESPONSE, + new AsyncResult( + null, new RadioCapability(0, 123456, 0, 0, "test_modem", 0), null)) + .sendToTarget(); + processAllMessages(); + assertFalse(mProxyController.isWakeLockHeld()); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java index 311fe2043e836eeae160a9886a2da9c07f8adf5f..2396d1dcc53b5e72002bb14ad8d3371f76100c35 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java @@ -16,6 +16,12 @@ package com.android.internal.telephony; +import static android.telephony.TelephonyManager.HAL_SERVICE_DATA; +import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; +import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO; +import static android.telephony.TelephonyManager.HAL_SERVICE_SIM; + import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ALLOW_DATA; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE; @@ -27,6 +33,7 @@ import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CONFERENCE import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DATA_REGISTRATION_STATE; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DELETE_SMS_ON_SIM; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IDENTITY; +import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IMEI; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENABLE_UICC_APPLICATIONS; import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION; @@ -90,6 +97,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -120,12 +128,14 @@ import android.hardware.radio.V1_6.IRadio; import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.LinkAddress; +import android.os.AsyncResult; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.RemoteException; import android.os.WorkSource; import android.service.carrier.CarrierIdentifier; import android.telephony.AccessNetworkConstants; @@ -172,6 +182,7 @@ import androidx.test.filters.FlakyTest; import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -183,8 +194,10 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -205,14 +218,16 @@ public class RILTest extends TelephonyTest { private RadioDataProxy mDataProxy; private RadioNetworkProxy mNetworkProxy; private RadioSimProxy mSimProxy; + private RadioModemProxy mRadioModemProxy; - private HalVersion mRadioVersionV10 = new HalVersion(1, 0); - private HalVersion mRadioVersionV11 = new HalVersion(1, 1); - private HalVersion mRadioVersionV12 = new HalVersion(1, 2); - private HalVersion mRadioVersionV13 = new HalVersion(1, 3); - private HalVersion mRadioVersionV14 = new HalVersion(1, 4); - private HalVersion mRadioVersionV15 = new HalVersion(1, 5); - private HalVersion mRadioVersionV16 = new HalVersion(1, 6); + private Map mHalVersionV10 = new HashMap<>(); + private Map mHalVersionV11 = new HashMap<>(); + private Map mHalVersionV12 = new HashMap<>(); + private Map mHalVersionV13 = new HashMap<>(); + private Map mHalVersionV14 = new HashMap<>(); + private Map mHalVersionV15 = new HashMap<>(); + private Map mHalVersionV16 = new HashMap<>(); + private Map mHalVersionV21 = new HashMap<>(); private RIL mRILInstance; private RIL mRILUnderTest; @@ -300,6 +315,7 @@ public class RILTest extends TelephonyTest { mDataProxy = mock(RadioDataProxy.class); mNetworkProxy = mock(RadioNetworkProxy.class); mSimProxy = mock(RadioSimProxy.class); + mRadioModemProxy = mock(RadioModemProxy.class); try { TelephonyDevController.create(); } catch (RuntimeException e) { @@ -316,10 +332,11 @@ public class RILTest extends TelephonyTest { doReturn(powerManager).when(context).getSystemService(Context.POWER_SERVICE); doReturn(new ApplicationInfo()).when(context).getApplicationInfo(); SparseArray proxies = new SparseArray<>(); - proxies.put(RIL.RADIO_SERVICE, null); - proxies.put(RIL.DATA_SERVICE, mDataProxy); - proxies.put(RIL.NETWORK_SERVICE, mNetworkProxy); - proxies.put(RIL.SIM_SERVICE, mSimProxy); + proxies.put(HAL_SERVICE_RADIO, null); + proxies.put(HAL_SERVICE_DATA, mDataProxy); + proxies.put(HAL_SERVICE_NETWORK, mNetworkProxy); + proxies.put(HAL_SERVICE_SIM, mSimProxy); + proxies.put(HAL_SERVICE_MODEM, mRadioModemProxy); mRILInstance = new RIL(context, RadioAccessFamily.getRafFromNetworkType(RILConstants.PREFERRED_NETWORK_MODE), Phone.PREFERRED_CDMA_SUBSCRIPTION, 0, proxies); @@ -331,11 +348,24 @@ public class RILTest extends TelephonyTest { eq(RadioNetworkProxy.class), any()); doReturn(mSimProxy).when(mRILUnderTest).getRadioServiceProxy(eq(RadioSimProxy.class), any()); + doReturn(mRadioModemProxy).when(mRILUnderTest).getRadioServiceProxy( + eq(RadioModemProxy.class), any()); doReturn(false).when(mDataProxy).isEmpty(); doReturn(false).when(mNetworkProxy).isEmpty(); doReturn(false).when(mSimProxy).isEmpty(); + doReturn(false).when(mRadioModemProxy).isEmpty(); try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV10); + for (int service = RIL.MIN_SERVICE_IDX; service <= RIL.MAX_SERVICE_IDX; service++) { + mHalVersionV10.put(service, new HalVersion(1, 0)); + mHalVersionV11.put(service, new HalVersion(1, 1)); + mHalVersionV12.put(service, new HalVersion(1, 2)); + mHalVersionV13.put(service, new HalVersion(1, 3)); + mHalVersionV14.put(service, new HalVersion(1, 4)); + mHalVersionV15.put(service, new HalVersion(1, 5)); + mHalVersionV16.put(service, new HalVersion(1, 6)); + mHalVersionV21.put(service, new HalVersion(2, 1)); + } + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV10); } catch (Exception e) { } } @@ -493,7 +523,7 @@ public class RILTest extends TelephonyTest { // Make radio version 1.5 to support the operation. try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } @@ -531,7 +561,7 @@ public class RILTest extends TelephonyTest { // Make radio version 1.5 to support the operation. try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } @@ -711,7 +741,7 @@ public class RILTest extends TelephonyTest { public void testStartNetworkScanWithUnsupportedResponse() throws Exception { // Use Radio HAL v1.5 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } NetworkScanRequest nsr = getNetworkScanRequestForTesting(); @@ -738,7 +768,7 @@ public class RILTest extends TelephonyTest { public void testGetVoiceRegistrationStateWithUnsupportedResponse() throws Exception { // Use Radio HAL v1.5 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } mRILUnderTest.getVoiceRegistrationState(obtainMessage()); @@ -773,7 +803,7 @@ public class RILTest extends TelephonyTest { public void testGetDataRegistrationStateWithUnsupportedResponse() throws Exception { // Use Radio HAL v1.5 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } @@ -818,7 +848,7 @@ public class RILTest extends TelephonyTest { // Use Radio HAL v1.6 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16); } catch (Exception e) { } @@ -861,7 +891,7 @@ public class RILTest extends TelephonyTest { public void testSendSMS_1_6() throws Exception { // Use Radio HAL v1.6 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16); } catch (Exception e) { } String smscPdu = "smscPdu"; @@ -893,7 +923,7 @@ public class RILTest extends TelephonyTest { public void testSendSMSExpectMore_1_6() throws Exception { // Use Radio HAL v1.6 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16); } catch (Exception e) { } String smscPdu = "smscPdu"; @@ -912,7 +942,7 @@ public class RILTest extends TelephonyTest { public void testSendCdmaSMS_1_6() throws Exception { // Use Radio HAL v1.6 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16); } catch (Exception e) { } byte[] pdu = "000010020000000000000000000000000000000000".getBytes(); @@ -928,7 +958,7 @@ public class RILTest extends TelephonyTest { public void testSendCdmaSMSExpectMore_1_6() throws Exception { // Use Radio HAL v1.6 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16); } catch (Exception e) { } byte[] pdu = "000010020000000000000000000000000000000000".getBytes(); @@ -1251,7 +1281,7 @@ public class RILTest extends TelephonyTest { @Test public void testIccCloseLogicalChannel() throws Exception { int channel = 1; - mRILUnderTest.iccCloseLogicalChannel(channel, obtainMessage()); + mRILUnderTest.iccCloseLogicalChannel(channel, false, obtainMessage()); verify(mRadioProxy).iccCloseLogicalChannel(mSerialNumberCaptor.capture(), eq(channel)); verifyRILResponse( mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SIM_CLOSE_CHANNEL); @@ -1475,7 +1505,7 @@ public class RILTest extends TelephonyTest { // Make radio version 1.5 to support the operation. try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } mRILUnderTest.getBarringInfo(obtainMessage()); @@ -2838,7 +2868,7 @@ public class RILTest extends TelephonyTest { // Make radio version 1.5 to support the operation. try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } mRILUnderTest.enableUiccApplications(false, obtainMessage()); @@ -2855,7 +2885,7 @@ public class RILTest extends TelephonyTest { // Make radio version 1.5 to support the operation. try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15); } catch (Exception e) { } mRILUnderTest.areUiccApplicationsEnabled(obtainMessage()); @@ -2902,7 +2932,7 @@ public class RILTest extends TelephonyTest { public void testGetSlicingConfig() throws Exception { // Use Radio HAL v1.6 try { - replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16); + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16); } catch (Exception e) { } mRILUnderTest.getSlicingConfig(obtainMessage()); @@ -2910,4 +2940,31 @@ public class RILTest extends TelephonyTest { verifyRILResponse_1_6( mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_GET_SLICING_CONFIG); } + + @Test + public void getImei() throws RemoteException { + try { + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV21); + } catch (Exception e) { + fail(); + } + mRILUnderTest.getImei(obtainMessage()); + verify(mRadioModemProxy, atLeast(1)).getImei(mSerialNumberCaptor.capture()); + verifyRILResponse(mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_DEVICE_IMEI); + } + + @Test + public void getImeiNotSupported() { + try { + replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16); + } catch (Exception e) { + fail(); + } + Message message = obtainMessage(); + mRILUnderTest.getImei(message); + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertEquals(null, ar.result); + Assert.assertNotNull(ar.exception.getMessage()); + Assert.assertEquals("REQUEST_NOT_SUPPORTED", ar.exception.getMessage()); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java index fa48f97c4f31a60903884b63c7ca963d976887e6..846b48e3f70ebf1678de5265ad9e714e52ea07d4 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java @@ -18,6 +18,7 @@ package com.android.internal.telephony; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -92,6 +93,7 @@ import androidx.test.filters.FlakyTest; import com.android.internal.R; 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.subscription.SubscriptionInfoInternal; @@ -106,6 +108,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -263,7 +266,6 @@ public class ServiceStateTrackerTest extends TelephonyTest { replaceInstance(ProxyController.class, "sProxyController", null, mProxyController); mBundle = mContextFixture.getCarrierConfigBundle(); - when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(mBundle); when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle); mBundle.putStringArray( @@ -294,6 +296,11 @@ public class ServiceStateTrackerTest extends TelephonyTest { waitUntilReady(); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); + // Voice radio tech change will always trigger an update of + // phone object irrespective of this config + mContextFixture.putBooleanResource( + com.android.internal.R.bool.config_switch_phone_on_voice_reg_state_change, false); + // Override SPN related resource mContextFixture.putResource( com.android.internal.R.string.lockscreen_carrier_default, @@ -487,30 +494,37 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @MediumTest public void testSetRadioPowerForReason() { + testSetRadioPowerForReason(TelephonyManager.RADIO_POWER_REASON_THERMAL); + testSetRadioPowerForReason(TelephonyManager.RADIO_POWER_REASON_NEARBY_DEVICE); + testSetRadioPowerForReason(TelephonyManager.RADIO_POWER_REASON_CARRIER); + } + + private void testSetRadioPowerForReason(int reason) { // Radio does not turn on if off for other reason and not emergency call. assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON); assertTrue(sst.getRadioPowerOffReasons().isEmpty()); - sst.setRadioPowerForReason(false, false, false, false, Phone.RADIO_POWER_REASON_THERMAL); - assertTrue(sst.getRadioPowerOffReasons().contains(Phone.RADIO_POWER_REASON_THERMAL)); + sst.setRadioPowerForReason(false, false, false, false, reason); + assertTrue(sst.getRadioPowerOffReasons().contains(reason)); assertTrue(sst.getRadioPowerOffReasons().size() == 1); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF); - sst.setRadioPowerForReason(true, false, false, false, Phone.RADIO_POWER_REASON_USER); - assertTrue(sst.getRadioPowerOffReasons().contains(Phone.RADIO_POWER_REASON_THERMAL)); + sst.setRadioPowerForReason(true, false, false, false, + TelephonyManager.RADIO_POWER_REASON_USER); + assertTrue(sst.getRadioPowerOffReasons().contains(reason)); assertTrue(sst.getRadioPowerOffReasons().size() == 1); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF); // Radio power state reason is removed and radio turns on if turned on for same reason it // had been turned off for. - sst.setRadioPowerForReason(true, false, false, false, Phone.RADIO_POWER_REASON_THERMAL); + sst.setRadioPowerForReason(true, false, false, false, reason); assertTrue(sst.getRadioPowerOffReasons().isEmpty()); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON); // Turn radio off, then successfully turn radio on for emergency call. - sst.setRadioPowerForReason(false, false, false, false, Phone.RADIO_POWER_REASON_THERMAL); - assertTrue(sst.getRadioPowerOffReasons().contains(Phone.RADIO_POWER_REASON_THERMAL)); + sst.setRadioPowerForReason(false, false, false, false, reason); + assertTrue(sst.getRadioPowerOffReasons().contains(reason)); assertTrue(sst.getRadioPowerOffReasons().size() == 1); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF); @@ -522,33 +536,83 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @MediumTest - public void testSetRadioPowerFromCarrier() { + public void testSetRadioPowerForMultipleReasons() { + assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON); + assertTrue(sst.getRadioPowerOffReasons().isEmpty()); + + // Turn off radio + turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_USER, 1); + turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_THERMAL, 2); + turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_CARRIER, 3); + turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_NEARBY_DEVICE, 4); + + // Turn on radio + turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_NEARBY_DEVICE, 3, + TelephonyManager.RADIO_POWER_OFF); + turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_THERMAL, 2, + TelephonyManager.RADIO_POWER_OFF); + turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_CARRIER, 1, + TelephonyManager.RADIO_POWER_OFF); + turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_USER, 0, + TelephonyManager.RADIO_POWER_ON); + } + + private void turnRadioOffForReason(int reason, int powerOffReasonSize) { + sst.setRadioPowerForReason(false, false, false, false, reason); + assertTrue(sst.getRadioPowerOffReasons().contains(reason)); + assertTrue(sst.getRadioPowerOffReasons().size() == powerOffReasonSize); + waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); + assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF); + } + + private void turnRadioOnForReason(int reason, int powerOffReasonSize, + int expectedRadioPowerState) { + sst.setRadioPowerForReason(true, false, false, false, reason); + assertFalse(sst.getRadioPowerOffReasons().contains(reason)); + assertTrue(sst.getRadioPowerOffReasons().size() == powerOffReasonSize); + waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); + assertTrue(mSimulatedCommands.getRadioState() == expectedRadioPowerState); + } + + @Test + @MediumTest + public void testSetRadioPowerForReasonCarrier() { // Carrier disable radio power - sst.setRadioPowerFromCarrier(false); + sst.setRadioPowerForReason(false, false, false, false, + TelephonyManager.RADIO_POWER_REASON_CARRIER); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertFalse(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON); - assertTrue(sst.getDesiredPowerState()); + assertFalse(sst.getDesiredPowerState()); assertFalse(sst.getPowerStateFromCarrier()); + assertTrue(sst.getRadioPowerOffReasons().contains( + TelephonyManager.RADIO_POWER_REASON_CARRIER)); + assertTrue(sst.getRadioPowerOffReasons().size() == 1); // User toggle radio power will not overrides carrier settings sst.setRadioPower(true); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertFalse(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON); - assertTrue(sst.getDesiredPowerState()); + assertFalse(sst.getDesiredPowerState()); assertFalse(sst.getPowerStateFromCarrier()); + assertTrue(sst.getRadioPowerOffReasons().contains( + TelephonyManager.RADIO_POWER_REASON_CARRIER)); + assertTrue(sst.getRadioPowerOffReasons().size() == 1); // Carrier re-enable radio power - sst.setRadioPowerFromCarrier(true); + sst.setRadioPowerForReason(true, false, false, false, + TelephonyManager.RADIO_POWER_REASON_CARRIER); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON); assertTrue(sst.getDesiredPowerState()); assertTrue(sst.getPowerStateFromCarrier()); + assertTrue(sst.getRadioPowerOffReasons().isEmpty()); // User toggle radio power off (airplane mode) and set carrier on sst.setRadioPower(false); - sst.setRadioPowerFromCarrier(true); + sst.setRadioPowerForReason(true, false, false, false, + TelephonyManager.RADIO_POWER_REASON_CARRIER); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); assertFalse(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON); @@ -850,13 +914,15 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @MediumTest public void testUpdatePhoneType() { + String brandOverride = "spn from brand override"; + doReturn(brandOverride).when(mUiccProfile).getOperatorBrandOverride(); doReturn(false).when(mPhone).isPhoneTypeGsm(); doReturn(true).when(mPhone).isPhoneTypeCdmaLte(); doReturn(CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM).when(mCdmaSSM). getCdmaSubscriptionSource(); - logd("Calling updatePhoneType"); // switch to CDMA + logd("Calling updatePhoneType"); sst.updatePhoneType(); ArgumentCaptor integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class); @@ -901,7 +967,6 @@ public class ServiceStateTrackerTest extends TelephonyTest { mSimulatedCommands.setVoiceRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); mSimulatedCommands.notifyNetworkStateChanged(); - waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); // verify if registered handler has message posted to it @@ -1083,7 +1148,6 @@ public class ServiceStateTrackerTest extends TelephonyTest { mSimulatedCommands.notifyNetworkStateChanged(); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); - // verify if registered handler has message posted to it ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); verify(mTestHandler).sendMessageAtTime(messageArgumentCaptor.capture(), anyLong()); @@ -1207,7 +1271,7 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @MediumTest - public void testRegisterForVoiceRegStateOrRatChange() { + public void testRegisterForVoiceRegStateOrRatChange() throws Exception { NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) .setDomain(NetworkRegistrationInfo.DOMAIN_CS) @@ -1215,14 +1279,17 @@ public class ServiceStateTrackerTest extends TelephonyTest { .build(); sst.mSS.addNetworkRegistrationInfo(nri); + sst.mSS.setState(ServiceState.STATE_IN_SERVICE); sst.registerForVoiceRegStateOrRatChanged(mTestHandler, EVENT_VOICE_RAT_CHANGED, null); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); // Verify if message was posted to handler and value of result ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); - verify(mTestHandler).sendMessageAtTime(messageArgumentCaptor.capture(), anyLong()); + verify(mTestHandler) + .sendMessageAtTime(messageArgumentCaptor.capture(), anyLong()); assertEquals(EVENT_VOICE_RAT_CHANGED, messageArgumentCaptor.getValue().what); + assertEquals(new Pair(ServiceState.STATE_IN_SERVICE, ServiceState.RIL_RADIO_TECHNOLOGY_LTE), ((AsyncResult)messageArgumentCaptor.getValue().obj).result); @@ -1317,6 +1384,7 @@ public class ServiceStateTrackerTest extends TelephonyTest { mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); mSimulatedCommands.notifyNetworkStateChanged(); + waitForDelayedHandlerAction(mSSTTestHandler.getThreadHandler(), 500, 200); waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); // verify if registered handler has message posted to it @@ -1468,14 +1536,11 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @SmallTest - public void testSetPsNotifications() { + public void testSetPsNotifications() throws Exception { int subId = 1; sst.mSubId = subId; doReturn(subId).when(mSubInfo).getSubscriptionId(); - doReturn(mSubInfo).when(mSubscriptionController).getActiveSubscriptionInfo( - anyInt(), anyString(), nullable(String.class)); - final NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); mContextFixture.putBooleanResource( @@ -1486,6 +1551,9 @@ public class ServiceStateTrackerTest extends TelephonyTest { when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable); mContextFixture.putResource(com.android.internal.R.string.RestrictedOnDataTitle, "test1"); + // Make sure getState() condition returns in service, without this at logs at times found to + // be out of service + sst.mSS.setState(ServiceState.STATE_IN_SERVICE); sst.setNotification(ServiceStateTracker.PS_ENABLED); ArgumentCaptor notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); @@ -1501,12 +1569,10 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @SmallTest - public void testSetCsNotifications() { + public void testSetCsNotifications() throws Exception { int subId = 1; sst.mSubId = subId; doReturn(subId).when(mSubInfo).getSubscriptionId(); - doReturn(mSubInfo).when(mSubscriptionController) - .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class)); final NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -1519,6 +1585,9 @@ public class ServiceStateTrackerTest extends TelephonyTest { mContextFixture.putResource(com.android.internal.R.string.RestrictedOnAllVoiceTitle, "test2"); + // Make sure getState() condition returns in service, without this at logs at times found to + // be out of service + sst.mSS.setState(ServiceState.STATE_IN_SERVICE); sst.setNotification(ServiceStateTracker.CS_ENABLED); ArgumentCaptor notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); @@ -1534,12 +1603,10 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @SmallTest - public void testSetCsNormalNotifications() { + public void testSetCsNormalNotifications() throws Exception { int subId = 1; sst.mSubId = subId; doReturn(subId).when(mSubInfo).getSubscriptionId(); - doReturn(mSubInfo).when(mSubscriptionController) - .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class)); final NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -1551,6 +1618,9 @@ public class ServiceStateTrackerTest extends TelephonyTest { when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable); mContextFixture.putResource(com.android.internal.R.string.RestrictedOnNormalTitle, "test3"); + // Make sure getState() condition returns in service, without this at logs at times found to + // be out of service + sst.mSS.setState(ServiceState.STATE_IN_SERVICE); sst.setNotification(ServiceStateTracker.CS_NORMAL_ENABLED); ArgumentCaptor notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); @@ -1566,12 +1636,10 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test @SmallTest - public void testSetCsEmergencyNotifications() { + public void testSetCsEmergencyNotifications() throws Exception { int subId = 1; sst.mSubId = subId; doReturn(subId).when(mSubInfo).getSubscriptionId(); - doReturn(mSubInfo).when(mSubscriptionController) - .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class)); final NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -1584,6 +1652,9 @@ public class ServiceStateTrackerTest extends TelephonyTest { mContextFixture.putResource(com.android.internal.R.string.RestrictedOnEmergencyTitle, "test4"); + // Make sure mIsEmergencyOnly should be true, when setNotification notification type is + // CS_EMERGENCY_ENABLED notification + sst.mSS.setEmergencyOnly(true); sst.setNotification(ServiceStateTracker.CS_EMERGENCY_ENABLED); ArgumentCaptor notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); @@ -2201,16 +2272,16 @@ public class ServiceStateTrackerTest extends TelephonyTest { } @Test - public void testPhyChanBandwidthRatchetedOnPhyChanBandwidth() throws Exception { + public void testPhyChanBandwidthRatchetedOnPhyChanBandwidth() { // LTE Cell with bandwidth = 10000 CellIdentityLte cellIdentity10 = new CellIdentityLte(1, 1, 1, 1, new int[] {1, 2}, 10000, "1", "1", "test", "tst", Collections.emptyList(), null); sendRegStateUpdateForLteCellId(cellIdentity10); - assertTrue(Arrays.equals(new int[] {10000}, sst.mSS.getCellBandwidths())); + assertArrayEquals(new int[]{10000}, sst.mSS.getCellBandwidths()); sendPhyChanConfigChange(new int[] {10000, 5000}, TelephonyManager.NETWORK_TYPE_LTE, 1); - assertTrue(Arrays.equals(new int[] {10000, 5000}, sst.mSS.getCellBandwidths())); + assertArrayEquals(new int[]{10000, 5000}, sst.mSS.getCellBandwidths()); } @Test @@ -2222,7 +2293,43 @@ public class ServiceStateTrackerTest extends TelephonyTest { } @Test - public void testPhyChanBandwidthResetsOnOos() throws Exception { + public void testNotifyServiceStateChangedOnPhysicalConfigUpdates() { + mBundle.putBoolean( + CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, + true); + + // LTE Cell with bandwidth = 10000 + CellIdentityLte cellIdentity11 = + new CellIdentityLte(1, 1, 1, 1, new int[] {1, 2}, 10000, "1", "1", "test", + "tst", Collections.emptyList(), null); + + sendRegStateUpdateForLteCellId(cellIdentity11); + + // Notify Service State changed for BandWidth Change + sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_LTE, 1); + verify(mPhone, times(1)).notifyServiceStateChanged(any(ServiceState.class)); + + // Notify Service State changed for NR Connection Status: LTE -> NR + // with context id update + when(mPhone.getDataNetworkController().isInternetNetwork(eq(3))).thenReturn(true); + sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_NR, 1, + new int[][]{{0, 1}, {2, 3}}); + verify(mPhone, times(2)).notifyServiceStateChanged(any(ServiceState.class)); + + // Service State changed is not notified since no change + when(mPhone.getDataNetworkController().isInternetNetwork(eq(3))).thenReturn(true); + sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_NR, 1, + new int[][]{{0, 1}, {2, 3}}); + verify(mPhone, times(2)).notifyServiceStateChanged(any(ServiceState.class)); + + // Notify Service State changed for NR Connection Status: NR -> LTE + when(mPhone.getDataNetworkController().isInternetNetwork(eq(3))).thenReturn(true); + sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_LTE, 1); + verify(mPhone, times(3)).notifyServiceStateChanged(any(ServiceState.class)); + } + + @Test + public void testPhyChanBandwidthResetsOnOos() { testPhyChanBandwidthRatchetedOnPhyChanBandwidth(); LteVopsSupportInfo lteVopsSupportInfo = new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE, @@ -2256,7 +2363,7 @@ public class ServiceStateTrackerTest extends TelephonyTest { sendPhyChanConfigChange(new int[] {10000, 5000}, TelephonyManager.NETWORK_TYPE_NR, 0); sendRegStateUpdateForNrCellId(nrCi); - assertTrue(Arrays.equals(new int[] {10000, 5000}, sst.mSS.getCellBandwidths())); + assertArrayEquals(new int[] {10000, 5000}, sst.mSS.getCellBandwidths()); } /** @@ -2353,6 +2460,9 @@ public class ServiceStateTrackerTest extends TelephonyTest { public void testLocaleTrackerUpdateWithIWLANInService() { // Start state: Cell data only LTE + IWLAN final String[] OpNamesResult = new String[] { "carrier long", "carrier", "310310" }; + // Clear invocations for mLocaleTracker as precondition before test case execution & as part + // test setup + Mockito.clearInvocations(mLocaleTracker); changeRegStateWithIwlanOperatorNumeric(NetworkRegistrationInfo.REGISTRATION_STATE_HOME, TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, OpNamesResult, true); @@ -2736,13 +2846,20 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test public void testUpdateSpnDisplay_spnNotEmptyAndCrossSimCallingEnabled_showSpnOnly() { // GSM phone - doReturn(true).when(mPhone).isPhoneTypeGsm(); + final CellIdentityLte cellIdentityLte = + new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test", + "tst", Collections.emptyList(), null); // In Service ServiceState ss = new ServiceState(); ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE); ss.setDataRegState(ServiceState.STATE_IN_SERVICE); + //To By Pass RatRacheter + ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + cellIdentityLte, true)); sst.mSS = ss; // cross-sim-calling is enable @@ -2771,11 +2888,19 @@ public class ServiceStateTrackerTest extends TelephonyTest { public void testUpdateSpnDisplay_spnNotEmptyAndWifiCallingEnabled_showSpnOnly() { // GSM phone doReturn(true).when(mPhone).isPhoneTypeGsm(); + final CellIdentityLte cellIdentityLte = + new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test", + "tst", Collections.emptyList(), null); // In Service ServiceState ss = new ServiceState(); ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE); ss.setDataRegState(ServiceState.STATE_IN_SERVICE); + //To By Pass RatRacheter + ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + cellIdentityLte, true)); sst.mSS = ss; // wifi-calling is enabled @@ -3012,6 +3137,7 @@ public class ServiceStateTrackerTest extends TelephonyTest { @Test public void testGetCombinedRegState() { doReturn(mImsPhone).when(mPhone).getImsPhone(); + doReturn(true).when(mPhone).isPhoneTypeGsm(); // If voice/data out of service, return out of service. doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState(); @@ -3051,4 +3177,35 @@ public class ServiceStateTrackerTest extends TelephonyTest { doReturn(ServiceState.STATE_EMERGENCY_ONLY).when(mServiceState).getState(); assertEquals(ServiceState.STATE_EMERGENCY_ONLY, sst.getCombinedRegState(mServiceState)); } + + + @Test + public void testOnChangingPreferredOnIwlan() throws Exception { + Field callbackField = + ServiceStateTracker.class.getDeclaredField("mAccessNetworksManagerCallback"); + callbackField.setAccessible(true); + AccessNetworksManager.AccessNetworksManagerCallback accessNetworksManagerCallback = + (AccessNetworksManager.AccessNetworksManagerCallback) callbackField.get(sst); + + when(mAccessNetworksManager.isAnyApnOnIwlan()).thenReturn(false); + + // Start state: Cell data only LTE + IWLAN + CellIdentityLte cellIdentity = + new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test", + "tst", Collections.emptyList(), null); + changeRegStateWithIwlan( + // WWAN + NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity, + TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE, + // WLAN + NetworkRegistrationInfo.REGISTRATION_STATE_HOME, + TelephonyManager.NETWORK_TYPE_IWLAN); + assertFalse(sst.mSS.isIwlanPreferred()); + + when(mAccessNetworksManager.isAnyApnOnIwlan()).thenReturn(true); + accessNetworksManagerCallback.onPreferredTransportChanged(0); + waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); + + assertTrue(sst.mSS.isIwlanPreferred()); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java index 0391a81c8c4a33de81e2e1b411c6c544fe879b74..e4617feaa85fdcbec06fd26a5bd918847a87cab7 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java @@ -18,7 +18,9 @@ package com.android.internal.telephony; import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP; import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI; +import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP; import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; import static com.google.common.truth.Truth.assertThat; @@ -27,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -110,6 +113,7 @@ public class SignalStrengthControllerTest extends TelephonyTest { -97, /* SIGNAL_STRENGTH_GOOD */ -89, /* SIGNAL_STRENGTH_GREAT */ }); + mBundle.putInt(CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT, 6); // Support EUTRAN with RSRP mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, 1 /* USE_RSRP */); @@ -120,6 +124,7 @@ public class SignalStrengthControllerTest extends TelephonyTest { -95, /* SIGNAL_STRENGTH_GOOD */ -85, /* SIGNAL_STRENGTH_GREAT */ }); + mBundle.putInt(CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, 3); // Support NR with SSRSRP mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, 1 /* USE_SSRSRP */); @@ -130,6 +135,7 @@ public class SignalStrengthControllerTest extends TelephonyTest { -80, /* SIGNAL_STRENGTH_GOOD */ -64, /* SIGNAL_STRENGTH_GREAT */ }); + mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 1); // By default, NR with SSRSRQ and SSSINR is not supported mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY, new int[] { @@ -514,6 +520,212 @@ public class SignalStrengthControllerTest extends TelephonyTest { CellSignalStrength.SIGNAL_STRENGTH_MODERATE); } + @Test + public void testSetMinimumHysteresisDb_FromThresholdDelta() { + final int[] consolidatedThresholdList = new int[] {-120, -116, -113, -112}; + + SignalThresholdInfo info = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_RSSI) + .setThresholds(new int[] {-113}, true) + .setHysteresisDb(2) + .build(); + SignalStrengthUpdateRequest request = + createTestSignalStrengthUpdateRequest( + info, + false /* shouldReportWhileIdle*/, + false /* shouldReportSystemWhileIdle */); + mSsc.setSignalStrengthUpdateRequest( + ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler)); + processAllMessages(); + + int minHysteresis = + mSsc.getMinimumHysteresisDb(true, + AccessNetworkConstants.AccessNetworkType.GERAN, + SIGNAL_MEASUREMENT_TYPE_RSSI, + consolidatedThresholdList); + assertEquals(1, minHysteresis); + mSsc.clearSignalStrengthUpdateRequest( + ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler)); + processAllMessages(); + } + + @Test + public void testSetMinimumHysteresisDb_FromSignalThresholdRequest() { + final int[] consolidatedThresholdList = new int[] {-120, -116, -112, -108}; + + SignalThresholdInfo info = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) + .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_RSRP) + .setThresholds(new int[] {-113}, true) + .setHysteresisDb(3) + .build(); + SignalStrengthUpdateRequest request = + createTestSignalStrengthUpdateRequest( + info, + false /* shouldReportWhileIdle*/, + false /* shouldReportSystemWhileIdle */); + mSsc.setSignalStrengthUpdateRequest( + ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler)); + processAllMessages(); + + int minHysteresis = + mSsc.getMinimumHysteresisDb(true, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + SIGNAL_MEASUREMENT_TYPE_RSRP, + consolidatedThresholdList); + assertEquals(3, minHysteresis); + + mSsc.clearSignalStrengthUpdateRequest( + ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler)); + processAllMessages(); + } + + @Test + public void testSetMinimumHysteresisDb_FromCarrierConfig() { + final int[] consolidatedThresholdList = new int[] {-120, -115, -108, -103}; + + SignalThresholdInfo info = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_SSRSRP) + .setThresholds(new int[] {-113}, true) + .setHysteresisDb(6) + .build(); + SignalStrengthUpdateRequest request = + createTestSignalStrengthUpdateRequest( + info, + false /* shouldReportWhileIdle*/, + false /* shouldReportSystemWhileIdle */); + mSsc.setSignalStrengthUpdateRequest( + ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler)); + processAllMessages(); + + int minHysteresis = + mSsc.getMinimumHysteresisDb(true, + AccessNetworkConstants.AccessNetworkType.NGRAN, + SIGNAL_MEASUREMENT_TYPE_SSRSRP, + consolidatedThresholdList); + assertEquals(1, minHysteresis); + mSsc.clearSignalStrengthUpdateRequest( + ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler)); + processAllMessages(); + } + + @Test + public void testSetHysteresisDb_WithCarrierConfigValue() { + when(mPhone.isDeviceIdle()).thenReturn(true); + when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID); + + mBundle.putInt(CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT, 5); + mBundle.putInt(CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, 3); + mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 2); + sendCarrierConfigUpdate(); + + ArgumentCaptor> signalThresholdInfoCaptor = + ArgumentCaptor.forClass(List.class); + verify(mSimulatedCommandsVerifier, atLeastOnce()) + .setSignalStrengthReportingCriteria(signalThresholdInfoCaptor.capture(), isNull()); + List capturedInfos = signalThresholdInfoCaptor.getAllValues().get(0); + assertThat(capturedInfos).isNotEmpty(); + + for (SignalThresholdInfo signalThresholdInfo : capturedInfos) { + if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSRP) { + assertEquals(3, signalThresholdInfo.getHysteresisDb()); + } + if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSSI) { + assertEquals(5, signalThresholdInfo.getHysteresisDb()); + } + if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_SSRSRP) { + assertEquals(2, signalThresholdInfo.getHysteresisDb()); + } + } + reset(mSimulatedCommandsVerifier); + } + + @Test + public void testSetHysteresisDb_BetweenCarrierConfigSignalThresholdInfoThresholdDelta() { + SignalThresholdInfo info = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_SSRSRP) + .setThresholds(new int[] {-116}, true) + .setHysteresisDb(3) + .build(); + SignalStrengthUpdateRequest request = + createTestSignalStrengthUpdateRequest( + info, + false /* shouldReportWhileIdle*/, + false /* shouldReportSystemWhileIdle */); + mSsc.setSignalStrengthUpdateRequest( + ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler)); + processAllMessages(); + + reset(mSimulatedCommandsVerifier); + when(mPhone.isDeviceIdle()).thenReturn(false); + when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID); + mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY, + new int[] { + -113, /* SIGNAL_STRENGTH_POOR */ + -107, /* SIGNAL_STRENGTH_MODERATE */ + -100, /* SIGNAL_STRENGTH_GOOD */ + -95, /* SIGNAL_STRENGTH_GREAT */ + }); + + mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, + 1 /* USE_SSRSRP */); + mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 4); + sendCarrierConfigUpdate(); + + ArgumentCaptor> signalThresholdInfoCaptor = + ArgumentCaptor.forClass(List.class); + verify(mSimulatedCommandsVerifier, atLeastOnce()) + .setSignalStrengthReportingCriteria(signalThresholdInfoCaptor.capture(), isNull()); + List capturedInfos = signalThresholdInfoCaptor.getAllValues().get(0); + assertThat(capturedInfos).isNotEmpty(); + + for (SignalThresholdInfo signalThresholdInfo : capturedInfos) { + if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_SSRSRP) { + assertEquals(4, + mBundle.getInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT)); + assertEquals(3, signalThresholdInfo.getHysteresisDb()); + } + } + } + + @Test + public void testSetHysteresisDb_WithInvalidCarrierConfigValue() { + when(mPhone.isDeviceIdle()).thenReturn(true); + when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID); + + mBundle.putInt(CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT, -4); + mBundle.putInt(CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, -5); + mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, -2); + sendCarrierConfigUpdate(); + + ArgumentCaptor> signalThresholdInfoCaptor = + ArgumentCaptor.forClass(List.class); + verify(mSimulatedCommandsVerifier, atLeastOnce()) + .setSignalStrengthReportingCriteria(signalThresholdInfoCaptor.capture(), isNull()); + List capturedInfos = signalThresholdInfoCaptor.getAllValues().get(0); + assertThat(capturedInfos).isNotEmpty(); + + for (SignalThresholdInfo signalThresholdInfo : capturedInfos) { + if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSRP) { + assertEquals(2, signalThresholdInfo.getHysteresisDb()); + } + if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSSI) { + assertEquals(2, signalThresholdInfo.getHysteresisDb()); + } + if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_SSRSRP) { + assertEquals(2, signalThresholdInfo.getHysteresisDb()); + } + } + reset(mSimulatedCommandsVerifier); + } + @Test public void testLteSignalStrengthReportingCriteria_convertRssnrUnitFromTenDbToDB() { SignalStrength ss = new SignalStrength( @@ -817,7 +1029,7 @@ public class SignalStrengthControllerTest extends TelephonyTest { } } // Only check on RADIO hal 1.5 and above to make it less flaky - if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { + if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) { assertThat(expectedNonEmptyThreshold).isEqualTo(actualNonEmptyThreshold); } } @@ -864,4 +1076,4 @@ public class SignalStrengthControllerTest extends TelephonyTest { } return builder.build(); } -} +} \ No newline at end of file diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java index f15845cd4252cf4e3e21550026cab87b98062fd7..96184c5ec799fa3dfcaebea56f129ec9e7f98d39 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java @@ -98,7 +98,7 @@ public class SignalStrengthTest { new CellSignalStrengthWcdma(-94, 4, -102, -5), new CellSignalStrengthTdscdma(-95, 2, -103), new CellSignalStrengthLte(-85, -91, -6, -10, 1, 12, 1), - new CellSignalStrengthNr(-91, -6, 3, 1, NrCqiReport, -80, -7, 4)); + new CellSignalStrengthNr(-91, -6, 3, 1, NrCqiReport, -80, -7, 4, 1)); assertParcelingIsLossless(s); PersistableBundle bundle = new PersistableBundle(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java index b282c55cbf46177f1dcbf4b567ba9126f5a926e6..e22d7ab24e0461345c26041c6ca914ffc60966d8 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java @@ -42,69 +42,94 @@ import java.util.Set; public class SignalThresholdInfoTest extends TestCase { private static final int HYSTERESIS_DB = 2; private static final int HYSTERESIS_MS = 30; - private static final int[] SSRSRP_THRESHOLDS = new int[]{-120, -100, -80, -60}; + private static final int[] SSRSRP_THRESHOLDS = new int[] {-120, -100, -80, -60}; // Map of SignalMeasurementType to invalid thresholds edge values. // Each invalid value will be constructed with a thresholds array to test separately. - private static final Map> INVALID_THRESHOLDS_MAP = Map.of( - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, - List.of(SignalThresholdInfo.SIGNAL_RSSI_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_RSSI_MAX_VALUE + 1), - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP, - List.of(SignalThresholdInfo.SIGNAL_RSCP_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_RSCP_MAX_VALUE + 1), - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP, - List.of(SignalThresholdInfo.SIGNAL_RSRP_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_RSRP_MAX_VALUE + 1), - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ, - List.of(SignalThresholdInfo.SIGNAL_RSRQ_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_RSRQ_MAX_VALUE + 1), - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR, - List.of(SignalThresholdInfo.SIGNAL_RSSNR_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_RSSNR_MAX_VALUE + 1), - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP, - List.of(SignalThresholdInfo.SIGNAL_SSRSRP_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_SSRSRP_MAX_VALUE + 1), - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ, - List.of(SignalThresholdInfo.SIGNAL_SSRSRQ_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_SSRSRQ_MAX_VALUE + 1), - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR, - List.of(SignalThresholdInfo.SIGNAL_SSSINR_MIN_VALUE - 1, - SignalThresholdInfo.SIGNAL_SSSINR_MAX_VALUE + 1) - ); + private static final Map> INVALID_THRESHOLDS_MAP = + Map.of( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, + List.of( + SignalThresholdInfo.SIGNAL_RSSI_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_RSSI_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP, + List.of( + SignalThresholdInfo.SIGNAL_RSCP_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_RSCP_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP, + List.of( + SignalThresholdInfo.SIGNAL_RSRP_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_RSRP_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ, + List.of( + SignalThresholdInfo.SIGNAL_RSRQ_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_RSRQ_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR, + List.of( + SignalThresholdInfo.SIGNAL_RSSNR_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_RSSNR_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP, + List.of( + SignalThresholdInfo.SIGNAL_SSRSRP_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_SSRSRP_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ, + List.of( + SignalThresholdInfo.SIGNAL_SSRSRQ_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_SSRSRQ_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR, + List.of( + SignalThresholdInfo.SIGNAL_SSSINR_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_SSSINR_MAX_VALUE + 1), + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO, + List.of( + SignalThresholdInfo.SIGNAL_ECNO_MIN_VALUE - 1, + SignalThresholdInfo.SIGNAL_ECNO_MAX_VALUE + 1)); // Map of RAN to allowed SignalMeasurementType set. // RAN/TYPE pair will be used to verify the validation of the combo - private static final Map> VALID_RAN_TO_MEASUREMENT_TYPE_MAP = Map.of( - AccessNetworkConstants.AccessNetworkType.GERAN, - Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI), - AccessNetworkConstants.AccessNetworkType.CDMA2000, - Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI), - AccessNetworkConstants.AccessNetworkType.UTRAN, - Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP), - AccessNetworkConstants.AccessNetworkType.EUTRAN, - Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP, - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ, - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR), - AccessNetworkConstants.AccessNetworkType.NGRAN, - Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP, - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ, - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) - ); + private static final Map> VALID_RAN_TO_MEASUREMENT_TYPE_MAP = + Map.of( + AccessNetworkConstants.AccessNetworkType.GERAN, + Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI), + AccessNetworkConstants.AccessNetworkType.CDMA2000, + Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI), + AccessNetworkConstants.AccessNetworkType.UTRAN, + Set.of( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO), + AccessNetworkConstants.AccessNetworkType.EUTRAN, + Set.of( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR), + AccessNetworkConstants.AccessNetworkType.NGRAN, + Set.of( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)); // Deliberately picking up the max/min value in each range to test the edge cases - private final int[] mRssiThresholds = new int[]{-113, -103, -97, -51}; - private final int[] mRscpThresholds = new int[]{-120, -105, -95, -25}; - private final int[] mRsrpThresholds = new int[]{-140, -118, -108, -44}; - private final int[] mRsrqThresholds = new int[]{-34, -17, -14, 3}; - private final int[] mRssnrThresholds = new int[]{-20, 10, 20, 30}; - private final int[] mSsrsrpThresholds = new int[]{-140, -118, -98, -44}; - private final int[] mSsrsrqThresholds = new int[]{-43, -17, -14, 20}; - private final int[] mSssinrThresholds = new int[]{-23, -16, -10, 40}; - - private final int[][] mThresholds = {mRssiThresholds, mRscpThresholds, mRsrpThresholds, - mRsrqThresholds, mRssnrThresholds, mSsrsrpThresholds, mSsrsrqThresholds, - mSssinrThresholds}; + private final int[] mRssiThresholds = new int[] {-113, -103, -97, -51}; + private final int[] mRscpThresholds = new int[] {-120, -105, -95, -25}; + private final int[] mRsrpThresholds = new int[] {-140, -118, -108, -44}; + private final int[] mRsrqThresholds = new int[] {-34, -17, -14, 3}; + private final int[] mRssnrThresholds = new int[] {-20, 10, 20, 30}; + private final int[] mSsrsrpThresholds = new int[] {-140, -118, -98, -44}; + private final int[] mSsrsrqThresholds = new int[] {-43, -17, -14, 20}; + private final int[] mSssinrThresholds = new int[] {-23, -16, -10, 40}; + private final int[] mEcnoThresholds = new int[] {-24, -16, -8, 1}; + + private final int[][] mThresholds = { + mRssiThresholds, + mRscpThresholds, + mRsrpThresholds, + mRsrqThresholds, + mRssnrThresholds, + mSsrsrpThresholds, + mSsrsrqThresholds, + mSssinrThresholds, + mEcnoThresholds + }; @Test @SmallTest @@ -120,12 +145,14 @@ public class SignalThresholdInfoTest extends TestCase { .setIsEnabled(false) .build(); - assertEquals(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP, + assertEquals( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP, signalThresholdInfo.getSignalMeasurementType()); assertEquals(HYSTERESIS_MS, signalThresholdInfo.getHysteresisMs()); assertEquals(HYSTERESIS_DB, signalThresholdInfo.getHysteresisDb()); - assertEquals(Arrays.toString(SSRSRP_THRESHOLDS), Arrays.toString( - signalThresholdInfo.getThresholds())); + assertEquals( + Arrays.toString(SSRSRP_THRESHOLDS), + Arrays.toString(signalThresholdInfo.getThresholds())); assertFalse(signalThresholdInfo.isEnabled()); } @@ -160,66 +187,125 @@ public class SignalThresholdInfoTest extends TestCase { @SmallTest public void testGetSignalThresholdInfo() { ArrayList stList = new ArrayList<>(); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) - .setHysteresisMs(0) - .setHysteresisDb(0) - .setThresholds(new int[]{}, true /*isSystem*/) - .setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) - .setHysteresisMs(HYSTERESIS_MS).setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mRssiThresholds) - .setIsEnabled(false) - .build()); - - assertThat(stList.get(0).getThresholds()).isEqualTo(new int[]{}); - assertThat(stList.get(1).getSignalMeasurementType()).isEqualTo( - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) + .setHysteresisMs(0) + .setHysteresisDb(0) + .setThresholds(new int[] {}, true /*isSystem*/) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRssiThresholds) + .setIsEnabled(false) + .build()); + + assertThat(stList.get(0).getThresholds()).isEqualTo(new int[] {}); + assertThat(stList.get(1).getSignalMeasurementType()) + .isEqualTo(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI); assertThat(stList.get(1).getThresholds()).isEqualTo(mRssiThresholds); + assertThat(stList.get(0).getHysteresisDb()) + .isEqualTo(SignalThresholdInfo.HYSTERESIS_DB_MINIMUM); + assertThat(stList.get(1).getHysteresisDb()) + .isEqualTo(HYSTERESIS_DB); } @Test @SmallTest public void testEqualsSignalThresholdInfo() { - final int[] dummyThresholds = new int[]{-100, -90, -70, -60}; - final int[] dummyThreholdsDisordered = new int[]{-60, -90, -100, -70}; - SignalThresholdInfo st1 = new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(1).setSignalMeasurementType(1) - .setHysteresisMs(HYSTERESIS_MS).setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mRssiThresholds).setIsEnabled(false) - .build(); - SignalThresholdInfo st2 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(2) - .setSignalMeasurementType(2).setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB).setThresholds(mRssiThresholds).setIsEnabled(false) - .build(); - SignalThresholdInfo st3 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(1) - .setSignalMeasurementType(1).setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB).setThresholds(dummyThresholds).setIsEnabled(false) - .build(); - SignalThresholdInfo st4 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(1) - .setSignalMeasurementType(1).setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB).setThresholds(mRssiThresholds).setIsEnabled(false) - .build(); - SignalThresholdInfo st5 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(1) - .setSignalMeasurementType(1).setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB).setThresholds(dummyThreholdsDisordered) - .setIsEnabled(false).build(); - - //Return true if all SignalThresholdInfo values match. + final int[] dummyThresholds = new int[] {-100, -90, -70, -60}; + final int[] dummyThresholdsDisordered = new int[] {-60, -90, -100, -70}; + SignalThresholdInfo st1 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(1) + .setSignalMeasurementType(1) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRssiThresholds) + .setIsEnabled(false) + .build(); + SignalThresholdInfo st2 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(2) + .setSignalMeasurementType(2) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRssiThresholds) + .setIsEnabled(false) + .build(); + SignalThresholdInfo st3 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(1) + .setSignalMeasurementType(1) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(dummyThresholds) + .setIsEnabled(false) + .build(); + SignalThresholdInfo st4 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(1) + .setSignalMeasurementType(1) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRssiThresholds) + .setIsEnabled(false) + .build(); + SignalThresholdInfo st5 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(1) + .setSignalMeasurementType(1) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(dummyThresholdsDisordered) + .setIsEnabled(false) + .build(); + + // Return true if all SignalThresholdInfo values match. assertTrue(st1.equals(st1)); assertFalse(st1.equals(st2)); assertFalse(st1.equals(st3)); assertTrue(st1.equals(st4)); - //Threshold values ordering doesn't matter + // Threshold values ordering doesn't matter assertTrue(st3.equals(st5)); - //Return false if the object of argument is other than SignalThresholdInfo. + // Return false if the object of argument is other than SignalThresholdInfo. assertFalse(st1.equals(new String("test"))); } + @Test + @SmallTest + public void testHysteresisDbSettings_WithValidRange() { + SignalThresholdInfo st1 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) + .setThresholds(new int[] {}, true) + .build(); + SignalThresholdInfo st2 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) + .setThresholds(new int[] {}, true) + .setHysteresisDb(3) + .build(); + SignalThresholdInfo st3 = + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) + .setThresholds(new int[] {}, true) + .setHysteresisDb(1) + .build(); + assertThat(st1.getHysteresisDb()).isEqualTo(HYSTERESIS_DB); + assertThat(st2.getHysteresisDb()).isEqualTo(3); + assertThat(st3.getHysteresisDb()).isEqualTo(1); + } + @Test @SmallTest public void testBuilderWithValidParameters() { @@ -229,7 +315,7 @@ public class SignalThresholdInfoTest extends TestCase { SignalThresholdInfo st = stList.get(i); assertThat(st.getThresholds()).isEqualTo(mThresholds[i]); assertThat(st.getHysteresisMs()).isEqualTo(SignalThresholdInfo.HYSTERESIS_MS_DISABLED); - assertThat(st.getHysteresisDb()).isEqualTo(SignalThresholdInfo.HYSTERESIS_DB_DISABLED); + assertThat(st.getHysteresisDb()).isEqualTo(HYSTERESIS_DB); assertFalse(st.isEnabled()); } } @@ -238,33 +324,46 @@ public class SignalThresholdInfoTest extends TestCase { @SmallTest public void testBuilderWithInvalidParameter() { // Invalid signal measurement type - int[] invalidSignalMeasurementTypes = new int[]{-1, 0, 9}; + int[] invalidSignalMeasurementTypes = new int[] {-1, 0, 9}; for (int signalMeasurementType : invalidSignalMeasurementTypes) { buildWithInvalidParameterThrowException( - AccessNetworkConstants.AccessNetworkType.GERAN, signalMeasurementType, - new int[]{-1}); + AccessNetworkConstants.AccessNetworkType.GERAN, + signalMeasurementType, + new int[] {-1}, 2); } // Null thresholds array - buildWithInvalidParameterThrowException(AccessNetworkConstants.AccessNetworkType.GERAN, - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, null); + buildWithInvalidParameterThrowException( + AccessNetworkConstants.AccessNetworkType.GERAN, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, + null, 0); // Empty thresholds - buildWithInvalidParameterThrowException(AccessNetworkConstants.AccessNetworkType.GERAN, - SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, new int[]{}); - + buildWithInvalidParameterThrowException( + AccessNetworkConstants.AccessNetworkType.GERAN, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, + new int[] {}, 5); // Too long thresholds array - buildWithInvalidParameterThrowException(AccessNetworkConstants.AccessNetworkType.GERAN, + buildWithInvalidParameterThrowException( + AccessNetworkConstants.AccessNetworkType.GERAN, + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, + new int[] {-100, -90, -70, -60, -58}, 3); + + // Test Hysteresis Db invalid Range + buildWithInvalidParameterThrowException( + AccessNetworkConstants.AccessNetworkType.GERAN, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, - new int[]{-100, -90, -70, -60, -58}); + new int[] {-100, -90, -70, -60}, -1); // Thresholds value out of range for (int signalMeasurementType : INVALID_THRESHOLDS_MAP.keySet()) { List invalidThresholds = INVALID_THRESHOLDS_MAP.get(signalMeasurementType); for (int threshold : invalidThresholds) { - buildWithInvalidParameterThrowException(getValidRan(signalMeasurementType), - signalMeasurementType, new int[]{threshold}); + buildWithInvalidParameterThrowException( + getValidRan(signalMeasurementType), + signalMeasurementType, + new int[] {threshold}, 1); } } @@ -272,21 +371,23 @@ public class SignalThresholdInfoTest extends TestCase { for (int ran : VALID_RAN_TO_MEASUREMENT_TYPE_MAP.keySet()) { Set validTypes = VALID_RAN_TO_MEASUREMENT_TYPE_MAP.get(ran); for (int type = SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI; - type <= SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR; type++) { + type <= SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO; + type++) { if (!validTypes.contains(type)) { - buildWithInvalidParameterThrowException(ran, type, new int[]{-1}); + buildWithInvalidParameterThrowException(ran, type, new int[] {-1}, 2); } } } } - private void buildWithInvalidParameterThrowException(int ran, int signalMeasurementType, - int[] thresholds) { + private void buildWithInvalidParameterThrowException( + int ran, int signalMeasurementType, int[] thresholds, int hysteresisDb) { try { new SignalThresholdInfo.Builder() .setRadioAccessNetworkType(ran) .setSignalMeasurementType(signalMeasurementType) .setThresholds(thresholds) + .setHysteresisDb(hysteresisDb) .build(); fail("exception expected"); } catch (IllegalArgumentException | NullPointerException expected) { @@ -296,68 +397,90 @@ public class SignalThresholdInfoTest extends TestCase { private ArrayList buildSignalThresholdInfoWithAllFields() { ArrayList stList = new ArrayList<>(); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) - .setHysteresisMs(HYSTERESIS_MS).setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mRssiThresholds).setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP) - .setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mRscpThresholds) - .setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP) - .setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mRsrpThresholds) - .setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ) - .setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mRsrqThresholds) - .setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR) - .setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mRssnrThresholds) - .setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP) - .setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mSsrsrpThresholds) - .setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ) - .setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mSsrsrqThresholds) - .setIsEnabled(false) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) - .setHysteresisMs(HYSTERESIS_MS) - .setHysteresisDb(HYSTERESIS_DB) - .setThresholds(mSssinrThresholds) - .setIsEnabled(false) - .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRssiThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRscpThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRsrpThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRsrqThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mRssnrThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mSsrsrpThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mSsrsrqThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mSssinrThresholds) + .setIsEnabled(false) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO) + .setHysteresisMs(HYSTERESIS_MS) + .setHysteresisDb(HYSTERESIS_DB) + .setThresholds(mEcnoThresholds) + .setIsEnabled(false) + .build()); return stList; } @@ -365,46 +488,63 @@ public class SignalThresholdInfoTest extends TestCase { private ArrayList buildSignalThresholdInfoWithPublicFields() { ArrayList stList = new ArrayList<>(); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) - .setThresholds(mRssiThresholds) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP) - .setThresholds(mRscpThresholds) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP) - .setThresholds(mRsrpThresholds) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ) - .setThresholds(mRsrqThresholds) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR) - .setThresholds(mRssnrThresholds) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP) - .setThresholds(mSsrsrpThresholds) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ) - .setThresholds(mSsrsrqThresholds) - .build()); - stList.add(new SignalThresholdInfo.Builder() - .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) - .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) - .setThresholds(mSssinrThresholds) - .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) + .setThresholds(mRssiThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP) + .setThresholds(mRscpThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP) + .setThresholds(mRsrpThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ) + .setThresholds(mRsrqThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR) + .setThresholds(mRssnrThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP) + .setThresholds(mSsrsrpThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ) + .setThresholds(mSsrsrqThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN) + .setSignalMeasurementType( + SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) + .setThresholds(mSssinrThresholds) + .build()); + stList.add( + new SignalThresholdInfo.Builder() + .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN) + .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO) + .setThresholds(mEcnoThresholds) + .build()); return stList; } @@ -418,6 +558,7 @@ public class SignalThresholdInfoTest extends TestCase { case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI: return AccessNetworkConstants.AccessNetworkType.GERAN; case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP: + case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO: return AccessNetworkConstants.AccessNetworkType.UTRAN; case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP: case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ: diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java index 6a075da37aacbc3cf75cb29bd36c45044389f2af..1e4c9392ae3951c7427d4b7a5794d0750eb96839 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java @@ -21,6 +21,7 @@ import android.hardware.radio.RadioError; import android.hardware.radio.V1_0.DataRegStateResult; import android.hardware.radio.V1_0.SetupDataCallResult; import android.hardware.radio.V1_0.VoiceRegStateResult; +import android.hardware.radio.modem.ImeiInfo; import android.net.KeepalivePacketData; import android.net.LinkProperties; import android.os.AsyncResult; @@ -66,6 +67,7 @@ import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.RILUtils; import com.android.internal.telephony.RadioCapability; import com.android.internal.telephony.SmsResponse; +import com.android.internal.telephony.SrvccConnection; import com.android.internal.telephony.UUSInfo; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; @@ -186,8 +188,17 @@ public class SimulatedCommands extends BaseCommands public boolean mSetRadioPowerForEmergencyCall; public boolean mSetRadioPowerAsSelectedPhoneForEmergencyCall; + public boolean mCallWaitActivated = false; + private SrvccConnection[] mSrvccConnections; + // mode for Icc Sim Authentication private int mAuthenticationMode; + + private int[] mImsRegistrationInfo = new int[4]; + + private boolean mN1ModeEnabled = false; + private boolean mVonrEnabled = false; + //***** Constructor public SimulatedCommands() { @@ -1432,6 +1443,14 @@ public class SimulatedCommands extends BaseCommands @Override public void queryCallWaiting(int serviceClass, Message response) { + if (response != null && serviceClass == SERVICE_CLASS_NONE) { + int[] r = new int[2]; + r[0] = (mCallWaitActivated ? 1 : 0); + r[1] = (mCallWaitActivated ? SERVICE_CLASS_VOICE : SERVICE_CLASS_NONE); + resultSuccess(response, r); + return; + } + unimplemented(response); } @@ -1440,11 +1459,15 @@ public class SimulatedCommands extends BaseCommands * @param serviceClass is a sum of SERVICE_CLASS_* * @param response is callback message */ - @Override public void setCallWaiting(boolean enable, int serviceClass, Message response) { - unimplemented(response); + if ((serviceClass & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE) { + mCallWaitActivated = enable; + } + if (response != null) { + resultSuccess(response, null); + } } /** @@ -1477,12 +1500,17 @@ public class SimulatedCommands extends BaseCommands } @Override - public void setNetworkSelectionModeAutomatic(Message result) {unimplemented(result);} + public void setNetworkSelectionModeAutomatic(Message result) { + SimulatedCommandsVerifier.getInstance().setNetworkSelectionModeAutomatic(result); + mMockNetworkSelectionMode = 0; + } @Override public void exitEmergencyCallbackMode(Message result) {unimplemented(result);} @Override public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message result) { - unimplemented(result); + SimulatedCommandsVerifier.getInstance().setNetworkSelectionModeManual( + operatorNumeric, ran, result); + mMockNetworkSelectionMode = 1; } /** @@ -1499,10 +1527,13 @@ public class SimulatedCommands extends BaseCommands getNetworkSelectionModeCallCount.incrementAndGet(); int ret[] = new int[1]; - ret[0] = 0; + ret[0] = mMockNetworkSelectionMode; resultSuccess(result, ret); } + /** 0 for automatic selection and a 1 for manual selection. */ + private int mMockNetworkSelectionMode = 0; + private final AtomicInteger getNetworkSelectionModeCallCount = new AtomicInteger(0); @VisibleForTesting @@ -1791,6 +1822,16 @@ public class SimulatedCommands extends BaseCommands resultSuccess(response, new String[] {FAKE_IMEI, FAKE_IMEISV, FAKE_ESN, FAKE_MEID}); } + @Override + public void getImei(Message response) { + SimulatedCommandsVerifier.getInstance().getImei(response); + ImeiInfo imeiInfo = new ImeiInfo(); + imeiInfo.imei = FAKE_IMEI; + imeiInfo.svn = FAKE_IMEISV; + imeiInfo.type = ImeiInfo.ImeiType.SECONDARY; + resultSuccess(response, imeiInfo); + } + @Override public void getCDMASubscription(Message result) { @@ -1891,8 +1932,8 @@ public class SimulatedCommands extends BaseCommands @Override public void setCdmaBroadcastActivation(boolean activate, Message response) { - unimplemented(response); - + SimulatedCommandsVerifier.getInstance().setCdmaBroadcastActivation(activate, response); + resultSuccess(response, null); } @Override @@ -1903,7 +1944,8 @@ public class SimulatedCommands extends BaseCommands @Override public void setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs, Message response) { - unimplemented(response); + SimulatedCommandsVerifier.getInstance().setCdmaBroadcastConfig(configs, response); + resultSuccess(response, null); } public void forceDataDormancy(Message response) { @@ -1913,7 +1955,8 @@ public class SimulatedCommands extends BaseCommands @Override public void setGsmBroadcastActivation(boolean activate, Message response) { - unimplemented(response); + SimulatedCommandsVerifier.getInstance().setGsmBroadcastActivation(activate, response); + resultSuccess(response, null); } @@ -1921,7 +1964,7 @@ public class SimulatedCommands extends BaseCommands public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) { SimulatedCommandsVerifier.getInstance().setGsmBroadcastConfig(config, response); if (mSendSetGsmBroadcastConfigResponse) { - unimplemented(response); + resultSuccess(response, null); } } @@ -2135,19 +2178,18 @@ public class SimulatedCommands extends BaseCommands } @Override - public void iccCloseLogicalChannel(int channel, Message response) { + public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) { unimplemented(response); } @Override public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, - int p1, int p2, int p3, String data, - Message response) { + int p1, int p2, int p3, String data, boolean isEs10Command, Message response) { SimulatedCommandsVerifier.getInstance().iccTransmitApduLogicalChannel(channel, cla, - instruction, p1, p2, p3, data, response); - if(mIccIoResultForApduLogicalChannel!=null) { + instruction, p1, p2, p3, data, isEs10Command, response); + if (mIccIoResultForApduLogicalChannel != null) { resultSuccess(response, mIccIoResultForApduLogicalChannel); - }else { + } else { resultFail(response, null, new RuntimeException("IccIoResult not set")); } } @@ -2558,4 +2600,44 @@ public class SimulatedCommands extends BaseCommands PcoData response = new PcoData(cid, bearerProto, pcoId, contents); mPcoDataRegistrants.notifyRegistrants(new AsyncResult(null, response, null)); } + + @Override + public void setSrvccCallInfo(SrvccConnection[] srvccConnections, Message result) { + mSrvccConnections = srvccConnections; + } + + public SrvccConnection[] getSrvccConnections() { + return mSrvccConnections; + } + + @Override + public void updateImsRegistrationInfo(int regState, + int imsRadioTech, int suggestedAction, int capabilities, Message result) { + mImsRegistrationInfo[0] = regState; + mImsRegistrationInfo[1] = imsRadioTech; + mImsRegistrationInfo[2] = suggestedAction; + mImsRegistrationInfo[3] = capabilities; + } + + public int[] getImsRegistrationInfo() { + return mImsRegistrationInfo; + } + + @Override + public void setN1ModeEnabled(boolean enable, Message result) { + mN1ModeEnabled = enable; + } + + public boolean isN1ModeEnabled() { + return mN1ModeEnabled; + } + + @Override + public void isVoNrEnabled(Message message, WorkSource workSource) { + resultSuccess(message, (Object) mVonrEnabled); + } + + public void setVonrEnabled(boolean vonrEnable) { + mVonrEnabled = vonrEnable; + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java index 60add09651ba12d6c836bfdddedfc04a9e0ab7e6..4d1c1042513c62c5a9c78126320087ea1e5ae6b1 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java @@ -1159,6 +1159,11 @@ public class SimulatedCommandsVerifier implements CommandsInterface { } + @Override + public void getImei(Message response) { + + } + @Override public void getCDMASubscription(Message response) { @@ -1299,13 +1304,14 @@ public class SimulatedCommandsVerifier implements CommandsInterface { } @Override - public void iccCloseLogicalChannel(int channel, Message response) { + public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) { } @Override public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, - int p2, int p3, String data, Message response) { + int p2, int p3, String data, + boolean isEs10Command, Message response) { } diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java index a81a2528bb475219ca4bd4ef93437d19b9457835..09c4173fd5819e1896c919a1388216c08e48282a 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java @@ -16,6 +16,17 @@ package com.android.internal.telephony; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.pm.PackageManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -29,17 +40,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - - import java.util.ArrayList; @RunWith(AndroidTestingRunner.class) @@ -195,4 +195,48 @@ public class SmsControllerTest extends TelephonyTest { doReturn(false).when(mPhone).isInEcm(); } + + @Test + public void sendsendTextForSubscriberTest() { + int subId = 1; + doReturn(true).when(mSubscriptionManager) + .isSubscriptionAssociatedWithUser(eq(subId), any()); + + mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234", + null, "text", null, null, false, 0L, true, true); + verify(mIccSmsInterfaceManager, Mockito.times(1)) + .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true); + } + + @Test + public void sendTextForSubscriberTest_InteractAcrossUsers() { + int subId = 1; + // Sending text to subscriber should not fail when the caller has the + // INTERACT_ACROSS_USERS_FULL permission. + doReturn(false).when(mSubscriptionManager) + .isSubscriptionAssociatedWithUser(eq(subId), any()); + doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission( + eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)); + + mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234", + null, "text", null, null, false, 0L, true, true); + verify(mIccSmsInterfaceManager, Mockito.times(1)) + .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true); + } + + @Test + public void sendTextForSubscriberTestFail() { + int subId = 1; + // Sending text to subscriber should fail when the caller does not have the + // INTERACT_ACROSS_USERS_FULL permission and is not associated with the subscription. + doReturn(false).when(mSubscriptionManager) + .isSubscriptionAssociatedWithUser(eq(subId), any()); + doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission( + eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)); + + mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234", + null, "text", null, null, false, 0L, true, true); + verify(mIccSmsInterfaceManager, Mockito.times(0)) + .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true); + } } \ No newline at end of file diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java index 6ef8508edfedc48c4452ef187a1061d9beff8d3e..b073cd43c33700e3610d6a634f9cdf42d3caa0fc 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java @@ -19,6 +19,8 @@ package com.android.internal.telephony; import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; @@ -28,13 +30,24 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; 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 static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncResult; +import android.os.Looper; import android.os.Message; import android.provider.Telephony.Sms.Intents; +import android.telephony.DisconnectCause; +import android.telephony.DomainSelectionService; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.PhoneNumberUtils; import android.telephony.SmsManager; import android.test.FlakyTest; import android.test.suitebuilder.annotation.SmallTest; @@ -42,36 +55,140 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Singleton; +import com.android.ims.ImsManager; +import com.android.internal.telephony.domainselection.DomainSelectionConnection; +import com.android.internal.telephony.domainselection.EmergencySmsDomainSelectionConnection; +import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection; +import com.android.internal.telephony.uicc.IccUtils; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import java.util.ArrayList; import java.util.HashMap; +import java.util.concurrent.CompletableFuture; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class SmsDispatchersControllerTest extends TelephonyTest { + /** + * Inherits the SmsDispatchersController to verify the protected methods. + */ + private static class TestSmsDispatchersController extends SmsDispatchersController { + TestSmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor, + SmsUsageMonitor usageMonitor, Looper looper) { + super(phone, storageMonitor, usageMonitor, looper); + } + + public DomainSelectionConnectionHolder testGetDomainSelectionConnectionHolder( + boolean emergency) { + return getDomainSelectionConnectionHolder(emergency); + } + + public void testSendData(String callingPackage, String destAddr, String scAddr, + int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, + boolean isForVvm) { + sendData(callingPackage, destAddr, scAddr, + destPort, data, sentIntent, deliveryIntent, isForVvm); + } + + public void testSendMultipartText(String destAddr, String scAddr, + ArrayList parts, ArrayList sentIntents, + ArrayList deliveryIntents, Uri messageUri, String callingPkg, + boolean persistMessage, int priority, boolean expectMore, int validityPeriod, + long messageId) { + sendMultipartText(destAddr, scAddr, parts, sentIntents, deliveryIntents, messageUri, + callingPkg, persistMessage, priority, expectMore, validityPeriod, messageId); + } + } + + /** + * Inherits the SMSDispatcher to verify the abstract or protected methods. + */ + protected abstract static class TestSmsDispatcher extends SMSDispatcher { + public TestSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController) { + super(phone, smsDispatchersController); + } + + @Override + public void sendData(String callingPackage, String destAddr, String scAddr, int destPort, + byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, + boolean isForVvm) { + super.sendData(callingPackage, destAddr, scAddr, destPort, + data, sentIntent, deliveryIntent, isForVvm); + } + + @Override + public void sendSms(SmsTracker tracker) { + } + + @Override + public String getFormat() { + return SmsConstants.FORMAT_3GPP; + } + } + + /** + * Inherits the SMSDispatcher to verify the protected methods. + */ + protected static class TestImsSmsDispatcher extends ImsSmsDispatcher { + public TestImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, + FeatureConnectorFactory factory) { + super(phone, smsDispatchersController, factory); + } + + @Override + public void sendData(String callingPackage, String destAddr, String scAddr, int destPort, + byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, + boolean isForVvm) { + super.sendData(callingPackage, destAddr, scAddr, destPort, + data, sentIntent, deliveryIntent, isForVvm); + } + + @Override + public String getFormat() { + return SmsConstants.FORMAT_3GPP; + } + } + + private static final String ACTION_TEST_SMS_SENT = "TEST_SMS_SENT"; + // Mocked classes private SMSDispatcher.SmsTracker mTracker; + private PendingIntent mSentIntent; + private TestImsSmsDispatcher mImsSmsDispatcher; + private TestSmsDispatcher mGsmSmsDispatcher; + private TestSmsDispatcher mCdmaSmsDispatcher; + private SmsDomainSelectionConnection mSmsDsc; + private EmergencySmsDomainSelectionConnection mEmergencySmsDsc; - private SmsDispatchersController mSmsDispatchersController; + private TestSmsDispatchersController mSmsDispatchersController; private boolean mInjectionCallbackTriggered = false; + private CompletableFuture mDscFuture; @Before public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); mTracker = mock(SMSDispatcher.SmsTracker.class); setupMockPackagePermissionChecks(); - - mSmsDispatchersController = new SmsDispatchersController(mPhone, mSmsStorageMonitor, - mSmsUsageMonitor); + mSmsDispatchersController = new TestSmsDispatchersController(mPhone, mSmsStorageMonitor, + mSmsUsageMonitor, mTestableLooper.getLooper()); + setUpDomainSelectionConnectionAsNotSupported(); processAllMessages(); } @After public void tearDown() throws Exception { + mImsSmsDispatcher = null; + mGsmSmsDispatcher = null; + mCdmaSmsDispatcher = null; + mSmsDsc = null; + mEmergencySmsDsc = null; + mDscFuture = null; mSmsDispatchersController.dispose(); mSmsDispatchersController = null; super.tearDown(); @@ -91,6 +208,30 @@ public class SmsDispatchersControllerTest extends TelephonyTest { assertTrue(mSmsDispatchersController.isIms()); } + @Test @SmallTest + public void testReportSmsMemoryStatus() throws Exception { + int eventReportMemoryStatusDone = 3; + SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone); + Message result = smsStorageMonnitor.obtainMessage(eventReportMemoryStatusDone); + ImsSmsDispatcher mImsSmsDispatcher = Mockito.mock(ImsSmsDispatcher.class); + mSmsDispatchersController.setImsSmsDispatcher(mImsSmsDispatcher); + mSmsDispatchersController.reportSmsMemoryStatus(result); + AsyncResult ar = (AsyncResult) result.obj; + verify(mImsSmsDispatcher).onMemoryAvailable(); + assertNull(ar.exception); + } + + @Test @SmallTest + public void testReportSmsMemoryStatusFailure() throws Exception { + int eventReportMemoryStatusDone = 3; + SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone); + Message result = smsStorageMonnitor.obtainMessage(eventReportMemoryStatusDone); + mSmsDispatchersController.setImsSmsDispatcher(null); + mSmsDispatchersController.reportSmsMemoryStatus(result); + AsyncResult ar = (AsyncResult) result.obj; + assertNotNull(ar.exception); + } + @Test @SmallTest @FlakyTest public void testSendImsGmsTest() throws Exception { switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM); @@ -179,6 +320,225 @@ public class SmsDispatchersControllerTest extends TelephonyTest { assertEquals(true, mInjectionCallbackTriggered); } + @Test @SmallTest + public void testSendImsGmsTestWithSmsc() { + IccSmsInterfaceManager iccSmsInterfaceManager = Mockito.mock(IccSmsInterfaceManager.class); + when(mPhone.getIccSmsInterfaceManager()).thenReturn(iccSmsInterfaceManager); + when(iccSmsInterfaceManager.getSmscAddressFromIccEf("com.android.messaging")) + .thenReturn("222"); + switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM); + + mSmsDispatchersController.sendText("111", null /*scAddr*/, TAG, + null, null, null, "com.android.messaging", + false, -1, false, -1, false, 0L); + byte[] smscbyte = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength( + "222"); + String smsc = IccUtils.bytesToHexString(smscbyte); + verify(mSimulatedCommandsVerifier).sendImsGsmSms(eq(smsc), anyString(), + anyInt(), anyInt(), any(Message.class)); + } + + @Test + @SmallTest + public void testSendDataWhenDomainPs() throws Exception { + sendDataWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false); + } + + @Test + @SmallTest + public void testSendDataWhenDomainCsAndCdma() throws Exception { + when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_CDMA); + sendDataWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, true); + } + + @Test + @SmallTest + public void testSendDataWhenDomainCsAndGsm() throws Exception { + when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); + sendDataWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false); + } + + @Test + @SmallTest + public void testSendTextWhenDomainPs() throws Exception { + sendTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false); + } + + @Test + @SmallTest + public void testSendTextWhenDomainCsAndCdma() throws Exception { + when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_CDMA); + sendTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, true); + } + + @Test + @SmallTest + public void testSendTextWhenDomainCsAndGsm() throws Exception { + when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); + sendTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false); + } + + @Test + @SmallTest + public void testSendMultipartTextWhenDomainPs() throws Exception { + sendMultipartTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false); + } + + @Test + @SmallTest + public void testSendMultipartTextWhenDomainCsAndCdma() throws Exception { + when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_CDMA); + sendMultipartTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, true); + } + + @Test + @SmallTest + public void testSendMultipartTextWhenDomainCsAndGsm() throws Exception { + when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); + sendMultipartTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false); + } + + @Test + @SmallTest + public void testSendRetrySmsWhenDomainPs() throws Exception { + sendRetrySmsWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, + PhoneConstants.PHONE_TYPE_GSM, SmsConstants.FORMAT_3GPP); + } + + @Test + @SmallTest + public void testSendRetrySmsWhenDomainCsAndCdma() throws Exception { + sendRetrySmsWithDomainSelection(NetworkRegistrationInfo.DOMAIN_CS, + PhoneConstants.PHONE_TYPE_CDMA, SmsConstants.FORMAT_3GPP2); + } + + @Test + @SmallTest + public void testSendRetrySmsWhenDomainCsAndGsm() throws Exception { + sendRetrySmsWithDomainSelection(NetworkRegistrationInfo.DOMAIN_CS, + PhoneConstants.PHONE_TYPE_GSM, SmsConstants.FORMAT_3GPP); + } + + @Test + @SmallTest + public void testSendRetrySmsWhenImsAlreadyUsedAndCdma() throws Exception { + sendRetrySmsWhenImsAlreadyUsed(PhoneConstants.PHONE_TYPE_CDMA, SmsConstants.FORMAT_3GPP2); + } + + @Test + @SmallTest + public void testSendRetrySmsWhenImsAlreadyUsedAndGsm() throws Exception { + sendRetrySmsWhenImsAlreadyUsed(PhoneConstants.PHONE_TYPE_GSM, SmsConstants.FORMAT_3GPP); + } + + @Test + @SmallTest + public void testSendEmergencyTextWhenDomainPs() throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + + mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null, + "test-app", false, 0, false, 10, false, 1L, false); + + SmsDispatchersController.DomainSelectionConnectionHolder holder = + mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true); + verify(mEmergencySmsDsc).requestDomainSelection(any(), any()); + assertNotNull(holder); + assertNotNull(holder.getConnection()); + assertTrue(holder.isEmergency()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(1, holder.getPendingRequests().size()); + + mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS); + processAllMessages(); + + verify(mEmergencySmsDsc).finishSelection(); + verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent), + any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false), + eq(1L), eq(false)); + assertNull(holder.getConnection()); + assertFalse(holder.isDomainSelectionRequested()); + assertEquals(0, holder.getPendingRequests().size()); + } + + @Test + @SmallTest + public void testNotifyDomainSelectionTerminated() throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + + mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null, + "test-app", false, 0, false, 10, false, 1L, false); + + SmsDispatchersController.DomainSelectionConnectionHolder holder = + mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false); + ArgumentCaptor captor = + ArgumentCaptor.forClass( + DomainSelectionConnection.DomainSelectionConnectionCallback.class); + verify(mSmsDsc).requestDomainSelection(any(), captor.capture()); + assertNotNull(holder); + assertNotNull(holder.getConnection()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(1, holder.getPendingRequests().size()); + + DomainSelectionConnection.DomainSelectionConnectionCallback callback = captor.getValue(); + assertNotNull(callback); + + mSmsDispatchersController.post(() -> { + callback.onSelectionTerminated(DisconnectCause.LOCAL); + }); + processAllMessages(); + + verify(mSmsDsc, never()).finishSelection(); + assertNull(holder.getConnection()); + assertFalse(holder.isDomainSelectionRequested()); + assertEquals(0, holder.getPendingRequests().size()); + + // We can use the IntentReceiver for receiving the sent result, but it can be reported as + // a flaky test since sometimes broadcasts can take a long time if the system is under load. + // At this point, we couldn't use the PendingIntent as a mock because it's a final class + // so this test checks the method in the IActivityManager when the PendingIntent#send(int) + // is called. + verify(mIActivityManager).sendIntentSender(any(), any(), any(), + eq(SmsManager.RESULT_ERROR_GENERIC_FAILURE), any(), any(), any(), any(), any()); + } + + @Test + @SmallTest + public void testSendTextContinuously() throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + + mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null, + "test-app", false, 0, false, 10, false, 1L, false); + + SmsDispatchersController.DomainSelectionConnectionHolder holder = + mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false); + assertNotNull(holder); + assertNotNull(holder.getConnection()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(1, holder.getPendingRequests().size()); + + mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null, + "test-app", false, 0, false, 10, false, 1L, false); + + verify(mSmsDsc).requestDomainSelection(any(), any()); + assertNotNull(holder.getConnection()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(2, holder.getPendingRequests().size()); + + mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS); + processAllMessages(); + + verify(mSmsDsc).finishSelection(); + verify(mImsSmsDispatcher, times(2)).sendText(eq("1111"), eq("2222"), eq("text"), + eq(mSentIntent), any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), + eq(false), eq(1L), eq(false)); + assertNull(holder.getConnection()); + assertFalse(holder.isDomainSelectionRequested()); + assertEquals(0, holder.getPendingRequests().size()); + } + private void switchImsSmsFormat(int phoneType) { mSimulatedCommands.setImsRegistrationState(new int[]{1, phoneType}); mSimulatedCommands.notifyImsNetworkStateChanged(); @@ -186,4 +546,249 @@ public class SmsDispatchersControllerTest extends TelephonyTest { processAllMessages(); assertTrue(mSmsDispatchersController.isIms()); } + + @Test + public void testSetImsManager() { + ImsManager imsManager = mock(ImsManager.class); + assertTrue(mSmsDispatchersController.setImsManager(imsManager)); + } + + private void setUpDomainSelectionConnectionAsNotSupported() { + mSmsDispatchersController.setDomainSelectionResolverProxy( + new SmsDispatchersController.DomainSelectionResolverProxy() { + @Override + @Nullable + public DomainSelectionConnection getDomainSelectionConnection(Phone phone, + @DomainSelectionService.SelectorType int selectorType, + boolean isEmergency) { + return null; + } + + @Override + public boolean isDomainSelectionSupported() { + return false; + } + }); + } + + private void setUpDomainSelectionConnection() { + mEmergencySmsDsc = Mockito.mock(EmergencySmsDomainSelectionConnection.class); + mSmsDsc = Mockito.mock(SmsDomainSelectionConnection.class); + mSmsDispatchersController.setDomainSelectionResolverProxy( + new SmsDispatchersController.DomainSelectionResolverProxy() { + @Override + @Nullable + public DomainSelectionConnection getDomainSelectionConnection(Phone phone, + @DomainSelectionService.SelectorType int selectorType, + boolean isEmergency) { + return isEmergency ? mEmergencySmsDsc : mSmsDsc; + } + + @Override + public boolean isDomainSelectionSupported() { + return true; + } + }); + + mDscFuture = new CompletableFuture<>(); + when(mSmsDsc.requestDomainSelection( + any(DomainSelectionService.SelectionAttributes.class), + any(DomainSelectionConnection.DomainSelectionConnectionCallback.class))) + .thenReturn(mDscFuture); + when(mEmergencySmsDsc.requestDomainSelection( + any(DomainSelectionService.SelectionAttributes.class), + any(DomainSelectionConnection.DomainSelectionConnectionCallback.class))) + .thenReturn(mDscFuture); + } + + private void setUpSmsDispatchers() throws Exception { + mImsSmsDispatcher = Mockito.mock(TestImsSmsDispatcher.class); + mGsmSmsDispatcher = Mockito.mock(TestSmsDispatcher.class); + mCdmaSmsDispatcher = Mockito.mock(TestSmsDispatcher.class); + + replaceInstance(SmsDispatchersController.class, "mImsSmsDispatcher", + mSmsDispatchersController, mImsSmsDispatcher); + replaceInstance(SmsDispatchersController.class, "mGsmDispatcher", + mSmsDispatchersController, mGsmSmsDispatcher); + replaceInstance(SmsDispatchersController.class, "mCdmaDispatcher", + mSmsDispatchersController, mCdmaSmsDispatcher); + + when(mTelephonyManager.isEmergencyNumber(eq("911"))).thenReturn(true); + + mSentIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0, + new Intent(ACTION_TEST_SMS_SENT), PendingIntent.FLAG_MUTABLE + | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT); + } + + private void sendDataWithDomainSelection(@NetworkRegistrationInfo.Domain int domain, + boolean isCdmaMo) throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + + byte[] data = new byte[] { 0x01 }; + mSmsDispatchersController.testSendData( + "test-app", "1111", "2222", 8080, data, mSentIntent, null, false); + + SmsDispatchersController.DomainSelectionConnectionHolder holder = + mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false); + verify(mSmsDsc).requestDomainSelection(any(), any()); + assertNotNull(holder); + assertNotNull(holder.getConnection()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(1, holder.getPendingRequests().size()); + + mDscFuture.complete(domain); + processAllMessages(); + + verify(mSmsDsc).finishSelection(); + if (domain == NetworkRegistrationInfo.DOMAIN_PS) { + verify(mImsSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080), + eq(data), eq(mSentIntent), any(), eq(false)); + } else if (isCdmaMo) { + verify(mCdmaSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080), + eq(data), eq(mSentIntent), any(), eq(false)); + } else { + verify(mGsmSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080), + eq(data), eq(mSentIntent), any(), eq(false)); + } + assertNull(holder.getConnection()); + assertFalse(holder.isDomainSelectionRequested()); + assertEquals(0, holder.getPendingRequests().size()); + } + + private void sendTextWithDomainSelection(@NetworkRegistrationInfo.Domain int domain, + boolean isCdmaMo) throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + + mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null, + "test-app", false, 0, false, 10, false, 1L, false); + + SmsDispatchersController.DomainSelectionConnectionHolder holder = + mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false); + verify(mSmsDsc).requestDomainSelection(any(), any()); + assertNotNull(holder); + assertNotNull(holder.getConnection()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(1, holder.getPendingRequests().size()); + + mDscFuture.complete(domain); + processAllMessages(); + + verify(mSmsDsc).finishSelection(); + if (domain == NetworkRegistrationInfo.DOMAIN_PS) { + verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent), + any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false), + eq(1L), eq(false)); + } else if (isCdmaMo) { + verify(mCdmaSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent), + any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false), + eq(1L), eq(false)); + } else { + verify(mGsmSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent), + any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false), + eq(1L), eq(false)); + } + assertNull(holder.getConnection()); + assertFalse(holder.isDomainSelectionRequested()); + assertEquals(0, holder.getPendingRequests().size()); + } + + private void sendMultipartTextWithDomainSelection(@NetworkRegistrationInfo.Domain int domain, + boolean isCdmaMo) throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + + ArrayList parts = new ArrayList<>(); + ArrayList sentIntents = new ArrayList<>(); + ArrayList deliveryIntents = new ArrayList<>(); + mSmsDispatchersController.testSendMultipartText("1111", "2222", parts, sentIntents, + deliveryIntents, null, "test-app", false, 0, false, 10, 1L); + + SmsDispatchersController.DomainSelectionConnectionHolder holder = + mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false); + verify(mSmsDsc).requestDomainSelection(any(), any()); + assertNotNull(holder); + assertNotNull(holder.getConnection()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(1, holder.getPendingRequests().size()); + + mDscFuture.complete(domain); + processAllMessages(); + + verify(mSmsDsc).finishSelection(); + if (domain == NetworkRegistrationInfo.DOMAIN_PS) { + verify(mImsSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts), + eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0), + eq(false), eq(10), eq(1L)); + } else if (isCdmaMo) { + verify(mCdmaSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts), + eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0), + eq(false), eq(10), eq(1L)); + } else { + verify(mGsmSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts), + eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0), + eq(false), eq(10), eq(1L)); + } + assertNull(holder.getConnection()); + assertFalse(holder.isDomainSelectionRequested()); + assertEquals(0, holder.getPendingRequests().size()); + } + + private void sendRetrySmsWithDomainSelection(@NetworkRegistrationInfo.Domain int domain, + int phoneType, String smsFormat) throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + when(mPhone.getPhoneType()).thenReturn(phoneType); + when(mImsSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP); + when(mCdmaSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP2); + when(mGsmSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP); + replaceInstance(SMSDispatcher.SmsTracker.class, "mFormat", mTracker, smsFormat); + + mSmsDispatchersController.sendRetrySms(mTracker); + + SmsDispatchersController.DomainSelectionConnectionHolder holder = + mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false); + verify(mSmsDsc).requestDomainSelection(any(), any()); + assertNotNull(holder); + assertNotNull(holder.getConnection()); + assertTrue(holder.isDomainSelectionRequested()); + assertEquals(1, holder.getPendingRequests().size()); + + mDscFuture.complete(domain); + processAllMessages(); + + verify(mSmsDsc).finishSelection(); + if (domain == NetworkRegistrationInfo.DOMAIN_PS) { + verify(mImsSmsDispatcher).sendSms(eq(mTracker)); + } else if (SmsConstants.FORMAT_3GPP2.equals(smsFormat)) { + verify(mCdmaSmsDispatcher).sendSms(eq(mTracker)); + } else { + verify(mGsmSmsDispatcher).sendSms(eq(mTracker)); + } + assertNull(holder.getConnection()); + assertFalse(holder.isDomainSelectionRequested()); + assertEquals(0, holder.getPendingRequests().size()); + } + + private void sendRetrySmsWhenImsAlreadyUsed(int phoneType, String smsFormat) throws Exception { + setUpDomainSelectionConnection(); + setUpSmsDispatchers(); + when(mPhone.getPhoneType()).thenReturn(phoneType); + when(mImsSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP); + when(mCdmaSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP2); + when(mGsmSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP); + replaceInstance(SMSDispatcher.SmsTracker.class, "mFormat", mTracker, smsFormat); + mTracker.mUsesImsServiceForIms = true; + + mSmsDispatchersController.sendRetrySms(mTracker); + + verify(mSmsDsc, never()).requestDomainSelection(any(), any()); + + if (SmsConstants.FORMAT_3GPP2.equals(smsFormat)) { + verify(mCdmaSmsDispatcher).sendSms(eq(mTracker)); + } else { + verify(mGsmSmsDispatcher).sendSms(eq(mTracker)); + } + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java index 2a7f35b04b7518aef7349a1a6e4c6b13a16c11f4..5057eea9e3a665fc98bb3b24a1f03de4a4a86b2d 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java @@ -78,7 +78,7 @@ public class SmsPermissionsTest extends TelephonyTest { } @Override - public boolean isCallerDefaultSmsPackage(String packageName) { + public boolean isCallerDefaultSmsPackage(String packageName, int uid) { return mCallerIsDefaultSmsPackage; } }; diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java index 3eace63e46ca9de43288c72e01eb40e2f5d4b7d9..b6775eb2ead3c576776d11dacefa6083b6449d69 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java @@ -20,8 +20,11 @@ import static com.android.internal.telephony.TelephonyTestUtils.waitForMs; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import android.content.Intent; +import android.content.res.Resources; import android.os.Message; import android.provider.Telephony; import android.test.suitebuilder.annotation.MediumTest; @@ -34,6 +37,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -137,6 +142,41 @@ public class SmsStorageMonitorTest extends TelephonyTest { .reportSmsMemoryStatus(eq(false), any(Message.class)); } + @Test @SmallTest + public void testReportSmsMemoryStatusToIms() { + Resources mockResources = Mockito.mock(Resources.class); + doReturn(mockResources).when(mContext).getResources(); + doReturn(true).when(mockResources).getBoolean(anyInt()); + doReturn(true).when(mIccSmsInterfaceManager.mDispatchersController).isIms(); + + mSimulatedCommands.notifyRadioOn(); + processAllMessages(); + + verify(mSimulatedCommandsVerifier, never()).reportSmsMemoryStatus(anyBoolean(), + any(Message.class)); + + // Send DEVICE_STORAGE_FULL + mContextFixture.getTestDouble().sendBroadcast( + new Intent(Intent.ACTION_DEVICE_STORAGE_FULL)); + processAllMessages(); + + verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(false), any(Message.class)); + assertFalse(mSmsStorageMonitor.isStorageAvailable()); + + mSimulatedCommands.notifyRadioOn(); + processAllMessages(); + + verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(false), any(Message.class)); + + // Send DEVICE_STORAGE_NOT_FULL + mContextFixture.getTestDouble().sendBroadcast( + new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)); + processAllMessages(); + + verify(mIccSmsInterfaceManager.mDispatchersController) + .reportSmsMemoryStatus(any(Message.class)); + } + @Test @SmallTest public void testReportSmsMemoryStatusDuringRetry() { mSimulatedCommands.setReportSmsMemoryStatusFailResponse(true); diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java deleted file mode 100644 index 2f8b1bb67774bafc48a45d982df875927af3cf7c..0000000000000000000000000000000000000000 --- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +++ /dev/null @@ -1,2302 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION; - -import static com.android.internal.telephony.SubscriptionController.REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID; -import static com.android.internal.telephony.uicc.IccCardStatus.CardState.CARDSTATE_PRESENT; - -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.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.Manifest; -import android.compat.testing.PlatformCompatChangeRule; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.ParcelUuid; -import android.os.PersistableBundle; -import android.os.UserHandle; -import android.provider.Settings; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.UiccPortInfo; -import android.telephony.UiccSlotInfo; -import android.test.mock.MockContentResolver; - -import androidx.test.filters.FlakyTest; -import androidx.test.filters.SmallTest; - -import com.android.internal.telephony.data.PhoneSwitcher; -import com.android.internal.telephony.uicc.IccCardStatus; -import com.android.internal.telephony.uicc.UiccController; -import com.android.internal.telephony.uicc.UiccSlot; - -import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; -import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.mockito.ArgumentCaptor; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -public class SubscriptionControllerTest extends TelephonyTest { - private static final int SINGLE_SIM = 1; - private static final int DUAL_SIM = 2; - private static final int FAKE_SUBID = 123; - private String mCallingPackage; - private String mCallingFeature; - private SubscriptionController mSubscriptionControllerUT; - private MockContentResolver mMockContentResolver; - private FakeTelephonyProvider mFakeTelephonyProvider; - private PersistableBundle mCarrierConfigs; - - // Mocked classes - private UiccSlot mUiccSlot; - private ITelephonyRegistry.Stub mTelephonyRegistryMock; - private MultiSimSettingController mMultiSimSettingControllerMock; - private ISetOpportunisticDataCallback mSetOpptDataCallback; - private Handler mHandler; - private SubscriptionInfo mMockSubscriptionInfo; - - private static final String MAC_ADDRESS_PREFIX = "mac_"; - private static final String DISPLAY_NAME_PREFIX = "my_phone_"; - - private static final String UNAVAILABLE_ICCID = ""; - private static final String UNAVAILABLE_NUMBER = ""; - private static final String DISPLAY_NUMBER = "123456"; - private static final String DISPLAY_NAME = "testing_display_name"; - - @Rule - public TestRule mCompatChangeRule = new PlatformCompatChangeRule(); - - @Before - public void setUp() throws Exception { - super.setUp(getClass().getSimpleName()); - enableSubscriptionManagerService(false); - mUiccSlot = mock(UiccSlot.class); - mTelephonyRegistryMock = mock(ITelephonyRegistry.Stub.class); - mMultiSimSettingControllerMock = mock(MultiSimSettingController.class); - mSetOpptDataCallback = mock(ISetOpportunisticDataCallback.class); - mHandler = mock(Handler.class); - mMockSubscriptionInfo = mock(SubscriptionInfo.class); - if (Looper.myLooper() == null) { - Looper.prepare(); - } - - doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount(); - doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount(); - mMockContentResolver = (MockContentResolver) mContext.getContentResolver(); - mFakeTelephonyProvider = new FakeTelephonyProvider(); - mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(), - mFakeTelephonyProvider); - replaceInstance(SubscriptionController.class, "sInstance", null, null); - replaceInstance(MultiSimSettingController.class, "sInstance", null, - mMultiSimSettingControllerMock); - - mSubscriptionControllerUT = SubscriptionController.init(mContext); - mCallingPackage = mContext.getOpPackageName(); - mCallingFeature = mContext.getAttributionTag(); - - doReturn(1).when(mProxyController).getMaxRafSupported(); - - // Carrier Config - mCarrierConfigs = mContextFixture.getCarrierConfigBundle(); - - mContextFixture.putIntArrayResource(com.android.internal.R.array.sim_colors, new int[]{5}); - setupMocksForTelephonyPermissions(Build.VERSION_CODES.R); - } - - @After - public void tearDown() throws Exception { - mContextFixture.addCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - /* Should clear fake content provider and resolver here */ - mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null); - - /* Clear sub info in mSubscriptionControllerUT since they will otherwise be persistent - * between each test case. */ - if (mSubscriptionControllerUT != null) { - mSubscriptionControllerUT.clearSubInfo(); - mSubscriptionControllerUT = null; - } - - /* Clear settings for default voice/data/sms sub ID */ - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - - mCallingPackage = null; - mCallingFeature = null; - mMockContentResolver = null; - mFakeTelephonyProvider = null; - mCarrierConfigs = null; - super.tearDown(); - } - - @Test @SmallTest - public void testInsertSim() { - //verify there is no sim inserted in the SubscriptionManager - assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage, - mCallingFeature)); - - int slotID = 0; - //insert one Subscription Info - mSubscriptionControllerUT.addSubInfo("test", null, slotID, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - //verify there is one sim - assertEquals(1, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage, - mCallingFeature)); - - //sanity for slot id and sub id - List mSubList = mSubscriptionControllerUT - .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature); - assertTrue(mSubList != null && mSubList.size() > 0); - for (int i = 0; i < mSubList.size(); i++) { - assertTrue(SubscriptionManager.isValidSubscriptionId( - mSubList.get(i).getSubscriptionId())); - assertTrue(SubscriptionManager.isValidSlotIndex(mSubList.get(i).getSimSlotIndex())); - } - } - - @Test @SmallTest - public void testUsageSettingProperty() { - testInsertSim(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - - /* Getting, there is no direct getter function for each fields of property */ - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - - // assertEquals(SubscriptionManager.USAGE_SETTING_UNKNOWN, subInfo.getUsageSetting()); - - assertThrows(IllegalArgumentException.class, - () -> mSubscriptionControllerUT.setUsageSetting( - SubscriptionManager.USAGE_SETTING_UNKNOWN, - subId, - mCallingPackage)); - - assertThrows(IllegalArgumentException.class, - () -> mSubscriptionControllerUT.setUsageSetting( - SubscriptionManager.USAGE_SETTING_DEFAULT, - SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, - mCallingPackage)); - - mSubscriptionControllerUT.setUsageSetting( - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - subId, - mCallingPackage); - - subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - assertEquals(SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, subInfo.getUsageSetting()); - } - - @Test @SmallTest - public void testChangeSIMProperty() { - int dataRoaming = 1; - int iconTint = 1; - String disName = "TESTING"; - String disNum = "12345"; - boolean isOpportunistic = true; - - testInsertSim(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - int subID = subIds[0]; - - /* Getting, there is no direct getter function for each fields of property */ - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature); - - /* Setting */ - mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID, - SubscriptionManager.NAME_SOURCE_USER_INPUT); - mSubscriptionControllerUT.setDataRoaming(dataRoaming, subID); - mSubscriptionControllerUT.setDisplayNumber(disNum, subID); - mSubscriptionControllerUT.setIconTint(iconTint, subID); - mSubscriptionControllerUT.setOpportunistic(isOpportunistic, subID, mCallingPackage); - - subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature); - - assertNotNull(subInfo); - assertEquals(dataRoaming, subInfo.getDataRoaming()); - assertEquals(disName, subInfo.getDisplayName()); - assertEquals(iconTint, subInfo.getIconTint()); - assertEquals(disNum, subInfo.getNumber()); - assertEquals(isOpportunistic, subInfo.isOpportunistic()); - } - - @Test @SmallTest - public void testSetGetDisplayNameSrc() { - testInsertSim(); - - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - int subID = subIds[0]; - - /* Setting */ - String disName = "TESTING"; - int nameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN; - mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID, nameSource); - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature); - assertNotNull(subInfo); - assertEquals(disName, subInfo.getDisplayName()); - assertEquals(nameSource, subInfo.getDisplayNameSource()); - } - - private void setSimEmbedded(boolean isEmbedded) throws Exception { - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, isEmbedded ? 1 : 0); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + getFirstSubId(), - null); - } - - @Test @SmallTest - public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithEmbeddedSim() - throws Exception { - testInsertSim(); - - // Set values of DB - setSimEmbedded(true); - int subId = getFirstSubId(); - int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER; - mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource); - - // Update with new value - String newDisplayName = "display_name_pnn"; - int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_PNN; - - // Save to DB after updated - mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - - assertNotNull(subInfo); - assertEquals(DISPLAY_NAME, subInfo.getDisplayName()); - assertEquals(nameSource, subInfo.getDisplayNameSource()); - } - - @Test @SmallTest - public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithConfigIsNull() - throws Exception { - testInsertSim(); - - // Set values of DB - setSimEmbedded(false); - int subId = getFirstSubId(); - int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER; - mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource); - - // Update with new value - String newDisplayName = "display_name_spn"; - int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN; - when(mCarrierConfigManager.getConfigForSubId(subId)).thenReturn(null); - - // Save to DB after updated - mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - - assertNotNull(subInfo); - assertEquals(DISPLAY_NAME, subInfo.getDisplayName()); - assertEquals(nameSource, subInfo.getDisplayNameSource()); - } - - @Test @SmallTest - public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithCarrierNameOverride() - throws Exception { - testInsertSim(); - - // Set values of DB - setSimEmbedded(false); - int subId = getFirstSubId(); - int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER; - mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource); - - // Update with new value - int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN; - String newDisplayName = "display_name_spn"; - mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, true); - - // Save to DB after updated - mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - - assertNotNull(subInfo); - assertEquals(DISPLAY_NAME, subInfo.getDisplayName()); - assertEquals(nameSource, subInfo.getDisplayNameSource()); - } - - @Test @SmallTest - public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithSpnAndCarrierName() - throws Exception { - testInsertSim(); - - // Set values of DB - setSimEmbedded(false); - int subId = getFirstSubId(); - int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER; - mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource); - - // Update with new value - int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN; - String carrierName = "testing_carrier_name"; - String newDisplayName = "display_name_spn"; - when(mUiccController.getUiccProfileForPhone(anyInt())).thenReturn(mUiccProfile); - when(mUiccProfile.getServiceProviderName()).thenReturn(null); - mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false); - mCarrierConfigs.putString(CarrierConfigManager.KEY_CARRIER_NAME_STRING, carrierName); - - // Save to DB after updated - mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - - assertNotNull(subInfo); - assertEquals(DISPLAY_NAME, subInfo.getDisplayName()); - assertEquals(nameSource, subInfo.getDisplayNameSource()); - } - - @Test @SmallTest - public void testSetGetDisplayNameSrc_updateNameSourcePnnToNameSourceCarrierId() - throws Exception { - testInsertSim(); - - // Set values of DB - int subId = getFirstSubId(); - int nameSource = SubscriptionManager.NAME_SOURCE_SIM_PNN; - mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource); - - // Update with new value - String newDisplayName = "display_name_carrier_id"; - int newNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID; - when(mPhone.getPlmn()).thenReturn("testing_pnn"); - - // Save to DB after updated - mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - - assertNotNull(subInfo); - assertEquals(DISPLAY_NAME, subInfo.getDisplayName()); - assertEquals(nameSource, subInfo.getDisplayNameSource()); - } - - @Test @SmallTest - public void testSetGetDisplayNameSrc_updateNameSourceUserInputToNameSourceSpn() - throws Exception { - testInsertSim(); - - // Set values of DB - int subId = getFirstSubId(); - int nameSource = SubscriptionManager.NAME_SOURCE_USER_INPUT; - mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource); - - // Update with new value - String newDisplayName = "display_name_spn"; - int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN; - - // Save to DB after updated - mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature); - - assertNotNull(subInfo); - assertEquals(DISPLAY_NAME, subInfo.getDisplayName()); - assertEquals(nameSource, subInfo.getDisplayNameSource()); - } - - @Test @SmallTest - public void testIsExistingNameSourceStillValid_pnnIsNotNull_returnTrue() { - when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID); - when(mMockSubscriptionInfo.getDisplayNameSource()) - .thenReturn(SubscriptionManager.NAME_SOURCE_SIM_PNN); - when(mPhone.getPlmn()).thenReturn("testing_pnn"); - - assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo)); - } - - @Test @SmallTest - public void testIsExistingNameSourceStillValid_spnIsNotNull_returnTrue() { - when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID); - when(mMockSubscriptionInfo.getDisplayNameSource()) - .thenReturn(SubscriptionManager.NAME_SOURCE_SIM_SPN); - when(mUiccController.getUiccProfileForPhone(anyInt())).thenReturn(mUiccProfile); - when(mUiccProfile.getServiceProviderName()).thenReturn("testing_spn"); - - assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo)); - } - - @Test @SmallTest - public void testIsExistingNameSourceStillValid_simIsEmbedded_returnTrue() { - when(mMockSubscriptionInfo.isEmbedded()).thenReturn(true); - when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID); - when(mMockSubscriptionInfo.getDisplayNameSource()) - .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER); - - assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo)); - } - - @Test @SmallTest - public void testIsExistingNameSourceStillValid_carrierConfigIsNull_returnTrue() { - when(mMockSubscriptionInfo.isEmbedded()).thenReturn(false); - when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID); - when(mMockSubscriptionInfo.getDisplayNameSource()) - .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER); - when(mCarrierConfigManager.getConfigForSubId(FAKE_SUBID)).thenReturn(null); - - assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo)); - } - - @Test @SmallTest - public void testIsExistingNameSourceStillValid_carrierNameOverrideIsTrue_returnTrue() { - when(mMockSubscriptionInfo.isEmbedded()).thenReturn(false); - when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID); - when(mMockSubscriptionInfo.getDisplayNameSource()) - .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER); - mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, true); - - assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo)); - } - - @Test @SmallTest - public void testIsExistingNameSourceStillValid_spnIsNullAndCarrierNameIsNotNull_returnTrue() { - when(mMockSubscriptionInfo.isEmbedded()).thenReturn(false); - when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID); - when(mMockSubscriptionInfo.getDisplayNameSource()) - .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER); - when(mUiccController.getUiccProfileForPhone(anyInt())).thenReturn(mUiccProfile); - when(mUiccProfile.getServiceProviderName()).thenReturn(null); - mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false); - mCarrierConfigs.putString(CarrierConfigManager.KEY_CARRIER_NAME_STRING, - "testing_carrier_name"); - - assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo)); - } - - @Test @SmallTest - public void testCleanUpSIM() { - testInsertSim(); - assertFalse(mSubscriptionControllerUT.isActiveSubId(2)); - mSubscriptionControllerUT.clearSubInfo(); - assertFalse(mSubscriptionControllerUT.isActiveSubId(1)); - assertEquals(SubscriptionManager.SIM_NOT_INSERTED, - mSubscriptionControllerUT.getSlotIndex(1)); - } - - @Test @SmallTest - public void testDefaultSubIdOnSingleSimDevice() { - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, - mSubscriptionControllerUT.getDefaultDataSubId()); - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, - mSubscriptionControllerUT.getDefaultSmsSubId()); - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, - mSubscriptionControllerUT.getDefaultSmsSubId()); - /* insert one sim */ - testInsertSim(); - // if support single sim, sms/data/voice default sub should be the same - assertEquals(1, mSubscriptionControllerUT.getDefaultSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId()); - } - - @Test @SmallTest - public void testSetGetMCCMNC() { - testInsertSim(); - String mCcMncVERIZON = "310004"; - mSubscriptionControllerUT.setMccMnc(mCcMncVERIZON, 1); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(1, mCallingPackage, mCallingFeature); - assertNotNull(subInfo); - assertEquals(Integer.parseInt(mCcMncVERIZON.substring(0, 3)), subInfo.getMcc()); - assertEquals(Integer.parseInt(mCcMncVERIZON.substring(3)), subInfo.getMnc()); - } - - @Test @SmallTest - public void testSetGetCarrierId() { - testInsertSim(); - int carrierId = 1234; - mSubscriptionControllerUT.setCarrierId(carrierId, 1); - - SubscriptionInfo subInfo = mSubscriptionControllerUT - .getActiveSubscriptionInfo(1, mCallingPackage, mCallingFeature); - assertNotNull(subInfo); - assertEquals(carrierId, subInfo.getCarrierId()); - } - - @Test - @SmallTest - public void testSetDefaultDataSubId() throws Exception { - doReturn(1).when(mPhone).getSubId(); - - mSubscriptionControllerUT.setDefaultDataSubId(1); - - ArgumentCaptor captorIntent = ArgumentCaptor.forClass(Intent.class); - verify(mContext, times(1)).sendStickyBroadcastAsUser( - captorIntent.capture(), eq(UserHandle.ALL)); - - Intent intent = captorIntent.getValue(); - assertEquals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED, intent.getAction()); - - Bundle b = intent.getExtras(); - - assertTrue(b.containsKey(PhoneConstants.SUBSCRIPTION_KEY)); - assertEquals(1, b.getInt(PhoneConstants.SUBSCRIPTION_KEY)); - } - - @Test - @SmallTest - public void testMigrateImsSettings() throws Exception { - testInsertSim(); - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - int subID = subIds[0]; - - // Set default void subId. - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, - subID); - - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.ENHANCED_4G_MODE_ENABLED, - 1); - - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.VT_IMS_ENABLED, - 0); - - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ENABLED, - 1); - - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_MODE, - 2); - - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ROAMING_MODE, - 3); - - mSubscriptionControllerUT.migrateImsSettings(); - - // Global settings should be all set. - assertEquals(-1, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.ENHANCED_4G_MODE_ENABLED)); - - assertEquals(-1, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.VT_IMS_ENABLED)); - - assertEquals(-1, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ENABLED)); - - assertEquals(-1, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_MODE)); - - assertEquals(-1, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ROAMING_MODE)); - - // The values should be migrated to its DB. - assertEquals("1", mSubscriptionControllerUT.getSubscriptionProperty( - subID, - SubscriptionManager.ENHANCED_4G_MODE_ENABLED, - mCallingPackage, - mCallingFeature)); - - assertEquals("0", mSubscriptionControllerUT.getSubscriptionProperty( - subID, - SubscriptionManager.VT_IMS_ENABLED, - mCallingPackage, - mCallingFeature)); - - assertEquals("1", mSubscriptionControllerUT.getSubscriptionProperty( - subID, - SubscriptionManager.WFC_IMS_ENABLED, - mCallingPackage, - mCallingFeature)); - - assertEquals("2", mSubscriptionControllerUT.getSubscriptionProperty( - subID, - SubscriptionManager.WFC_IMS_MODE, - mCallingPackage, - mCallingFeature)); - - assertEquals("3", mSubscriptionControllerUT.getSubscriptionProperty( - subID, - SubscriptionManager.WFC_IMS_ROAMING_MODE, - mCallingPackage, - mCallingFeature)); - } - - @Test - @SmallTest - public void testSkipMigrateImsSettings() throws Exception { - - // Set default invalid subId. - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, - -1); - - int enhanced4gModeEnabled = 1; - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.ENHANCED_4G_MODE_ENABLED, - enhanced4gModeEnabled); - - int vtImsEnabled = 0; - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.VT_IMS_ENABLED, - vtImsEnabled); - - int wfcImsEnabled = 1; - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ENABLED, - wfcImsEnabled); - - int wfcImsMode = 2; - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_MODE, - wfcImsMode); - - int wfcImsRoamingMode = 3; - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ROAMING_MODE, - wfcImsRoamingMode); - - mSubscriptionControllerUT.migrateImsSettings(); - - // Migration should be skipped because subId was invalid - assertEquals(enhanced4gModeEnabled, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.ENHANCED_4G_MODE_ENABLED)); - - assertEquals(vtImsEnabled, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.VT_IMS_ENABLED)); - - assertEquals(wfcImsEnabled, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ENABLED)); - - assertEquals(wfcImsMode, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_MODE)); - - assertEquals(wfcImsRoamingMode, Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WFC_IMS_ROAMING_MODE)); - } - - @Test - @SmallTest - public void testOpptSubInfoListChanged() throws Exception { - registerMockTelephonyRegistry(); - verify(mTelephonyRegistryManager, times(0)) - .notifyOpportunisticSubscriptionInfoChanged(); - - testInsertSim(); - mSubscriptionControllerUT.addSubInfo("test2", null, 0, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions - // should return empty list and no callback triggered. - List opptSubList = mSubscriptionControllerUT - .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature); - - assertTrue(opptSubList.isEmpty()); - verify(mTelephonyRegistryManager, times(0)) - .notifyOpportunisticSubscriptionInfoChanged(); - - // Setting sub2 as opportunistic should trigger callback. - mSubscriptionControllerUT.setOpportunistic(true, 2, mCallingPackage); - - verify(mTelephonyRegistryManager, times(1)) - .notifyOpportunisticSubscriptionInfoChanged(); - opptSubList = mSubscriptionControllerUT - .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature); - assertEquals(1, opptSubList.size()); - assertEquals("test2", opptSubList.get(0).getIccId()); - - // Changing non-opportunistic sub1 shouldn't trigger callback. - mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 1, - SubscriptionManager.NAME_SOURCE_SIM_SPN); - verify(mTelephonyRegistryManager, times(1)) - .notifyOpportunisticSubscriptionInfoChanged(); - - mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 2, - SubscriptionManager.NAME_SOURCE_SIM_SPN); - verify(mTelephonyRegistryManager, times(2)) - .notifyOpportunisticSubscriptionInfoChanged(); - } - - @Test @SmallTest - public void testInsertRemoteSim() { - makeThisDeviceMultiSimCapable(); - mContextFixture.addSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); - - // verify there are no sim's in the system. - assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage, - mCallingFeature)); - - addAndVerifyRemoteSimAddition(1, 0); - } - - private void addAndVerifyRemoteSimAddition(int num, int numOfCurrentSubs) { - // Verify the number of current subs in the system - assertEquals(numOfCurrentSubs, - mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage, mCallingFeature)); - - // if there are current subs in the system, get that info - List mSubList; - ArrayList macAddresses = new ArrayList<>(); - ArrayList displayNames = new ArrayList<>(); - if (numOfCurrentSubs > 0) { - mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - assertNotNull(mSubList); - assertEquals(numOfCurrentSubs, mSubList.size()); - for (SubscriptionInfo info : mSubList) { - assertNotNull(info.getIccId()); - assertNotNull(info.getDisplayName()); - macAddresses.add(info.getIccId()); - displayNames.add(info.getDisplayName().toString()); - } - } - - // To add more subs, we need to create macAddresses + displaynames. - for (int i = 0; i < num; i++) { - macAddresses.add(MAC_ADDRESS_PREFIX + (numOfCurrentSubs + i)); - displayNames.add(DISPLAY_NAME_PREFIX + (numOfCurrentSubs + i)); - } - - // Add subs - one at a time and verify the contents in subscription info data structs - for (int i = 0; i < num; i++) { - int index = numOfCurrentSubs + i; - mSubscriptionControllerUT.addSubInfo(macAddresses.get(index), displayNames.get(index), - SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB, - SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); - - // make sure the subscription is added in SubscriptionController data structs - Map> slotIndexToSubsMap = - mSubscriptionControllerUT.getSlotIndexToSubIdsMap(); - assertNotNull(slotIndexToSubsMap); - // Since All remote sim's go to the same slot index, there should only be one entry - assertEquals(1, slotIndexToSubsMap.size()); - - // get all the subscriptions available. should be what is just added in this method - // PLUS the number of subs that already existed before - int expectedNumOfSubs = numOfCurrentSubs + i + 1; - ArrayList subIdsList = - slotIndexToSubsMap.get(SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB); - assertNotNull(subIdsList); - assertEquals(expectedNumOfSubs, subIdsList.size()); - - // validate slot index, sub id etc - mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - assertNotNull(mSubList); - assertEquals(expectedNumOfSubs, mSubList.size()); - - // sort on subscription-id which will make sure the previously existing subscriptions - // are in earlier slots in the array - mSubList.sort(SUBSCRIPTION_INFO_COMPARATOR); - - // Verify the subscription data. Skip the verification for the existing subs. - for (int j = numOfCurrentSubs; j < mSubList.size(); j++) { - SubscriptionInfo info = mSubList.get(j); - assertTrue(SubscriptionManager.isValidSubscriptionId(info.getSubscriptionId())); - assertEquals(SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB, - info.getSimSlotIndex()); - assertEquals(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM, - info.getSubscriptionType()); - assertEquals(macAddresses.get(j), info.getIccId()); - assertEquals(displayNames.get(j), info.getDisplayName()); - } - } - } - - private static final Comparator SUBSCRIPTION_INFO_COMPARATOR = - Comparator.comparingInt(o -> o.getSubscriptionId()); - - @Test @SmallTest - public void testInsertMultipleRemoteSims() { - makeThisDeviceMultiSimCapable(); - mContextFixture.addSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); - - // verify that there are no subscription info records - assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage, - mCallingFeature)); - Map> slotIndexToSubsMap = - mSubscriptionControllerUT.getSlotIndexToSubIdsMap(); - assertNotNull(slotIndexToSubsMap); - assertTrue(slotIndexToSubsMap.isEmpty()); - - // Add a few subscriptions - addAndVerifyRemoteSimAddition(4, 0); - } - - @FlakyTest - @Test @SmallTest - public void testDefaultSubIdOnMultiSimDevice() { - makeThisDeviceMultiSimCapable(); - - // Initially, defaults should be -1 - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, - mSubscriptionControllerUT.getDefaultDataSubId()); - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, - mSubscriptionControllerUT.getDefaultSmsSubId()); - assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, - mSubscriptionControllerUT.getDefaultSmsSubId()); - - // Insert one Remote-Sim. - testInsertRemoteSim(); - - // defaults should be set to this newly-inserted subscription - assertEquals(1, mSubscriptionControllerUT.getDefaultSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId()); - - // Add a few subscriptions - addAndVerifyRemoteSimAddition(4, 1); - - // defaults should be still be set to the first sub - and unchanged by the addition of - // the above multiple sims. - assertEquals(1, mSubscriptionControllerUT.getDefaultSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId()); - assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId()); - } - - @Test @SmallTest - public void testRemoveSubscription() { - makeThisDeviceMultiSimCapable(); - - /* insert some sims */ - testInsertMultipleRemoteSims(); - assertEquals(1, mSubscriptionControllerUT.getDefaultSubId()); - int[] subIdsArray = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIdsArray.length > 0); - int len = subIdsArray.length; - - // remove the first sim - which also is the default sim. - int result = mSubscriptionControllerUT.removeSubInfo(MAC_ADDRESS_PREFIX + 0, - SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); - - assertTrue(result > 0); - // now check the number of subs left. should be one less than earlier - int[] newSubIdsArray = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(newSubIdsArray.length > 0); - assertEquals(len - 1, newSubIdsArray.length); - - // now check that there is a new default - assertNotSame(1, mSubscriptionControllerUT.getDefaultSubId()); - } - - private void makeThisDeviceMultiSimCapable() { - doReturn(10).when(mTelephonyManager).getSimCount(); - } - - @Test - @SmallTest - public void testSetSubscriptionGroupWithModifyPermission() throws Exception { - testInsertSim(); - mSubscriptionControllerUT.addSubInfo("test2", null, 0, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - - int[] subIdList = new int[] {1, 2}; - try { - mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - fail("createSubscriptionGroup should fail with no permission."); - } catch (SecurityException e) { - // Expected result. - } - - // With modify permission it should succeed. - mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); - ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - assertNotEquals(null, groupId); - - // Calling it again should generate a new group ID. - ParcelUuid newGroupId = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - assertNotEquals(null, newGroupId); - assertNotEquals(groupId, newGroupId); - } - - @Test - @SmallTest - public void testGetSubscriptionProperty() throws Exception { - testInsertSim(); - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.GROUP_UUID, 1); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 1, null); - - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - - // should succeed with read phone state permission - String prop = mSubscriptionControllerUT.getSubscriptionProperty(1, - SubscriptionManager.CB_EXTREME_THREAT_ALERT, mContext.getOpPackageName(), - mContext.getAttributionTag()); - - assertNotEquals(null, prop); - - // group UUID requires privileged phone state permission - prop = mSubscriptionControllerUT.getSubscriptionProperty(1, SubscriptionManager.GROUP_UUID, - mContext.getOpPackageName(), mContext.getAttributionTag()); - assertEquals(null, prop); - - // group UUID should succeed once privileged phone state permission is granted - setupReadPrivilegePermission(); - prop = mSubscriptionControllerUT.getSubscriptionProperty(1, SubscriptionManager.GROUP_UUID, - mContext.getOpPackageName(), mContext.getAttributionTag()); - assertNotEquals(null, prop); - } - - @Test - @SmallTest - public void testCreateSubscriptionGroupWithCarrierPrivilegePermission() throws Exception { - testInsertSim(); - // Adding a second profile and mark as embedded. - // TODO b/123300875 slot index 1 is not expected to be valid - mSubscriptionControllerUT.addSubInfo("test2", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, 1); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null); - mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); - - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - - int[] subIdList = new int[] {1, 2}; - // It should fail since it has no permission. - try { - mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - fail("createSubscriptionGroup should fail with no permission."); - } catch (SecurityException e) { - // Expected result. - } - - doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); - try { - mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - fail("createSubscriptionGroup should fail with no permission on sub 2."); - } catch (SecurityException e) { - // Expected result. - } - - doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); - ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - assertNotEquals(null, groupId); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mContext.getOpPackageName(), - mContext.getAttributionTag()); - - // Put sub3 into slot 1 to make sub2 inactive. - mContextFixture.addCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE); - // TODO b/123300875 slot index 1 is not expected to be valid - mSubscriptionControllerUT.addSubInfo("test3", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - mContextFixture.removeCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE); - // As sub2 is inactive, it will checks carrier privilege against access rules in the db. - doReturn(true).when(mSubscriptionManager).canManageSubscription( - eq(subInfoList.get(1)), anyString()); - - ParcelUuid newGroupId = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - assertNotEquals(null, newGroupId); - assertNotEquals(groupId, newGroupId); - } - - @Test - @SmallTest - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testAddSubscriptionIntoGroupWithCarrierPrivilegePermission() throws Exception { - testInsertSim(); - // Adding a second profile and mark as embedded. - // TODO b/123300875 slot index 1 is not expected to be valid - mSubscriptionControllerUT.addSubInfo("test2", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, 1); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null); - mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); - - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - - // Create group for sub 1. - int[] subIdList = new int[] {1}; - doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList(); - doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); - ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, "packageName1"); - - // Try to add sub 2 into group of sub 1. - // Should fail as it doesn't have carrier privilege on sub 2. - try { - mSubscriptionControllerUT.addSubscriptionsIntoGroup( - new int[] {2}, groupId, "packageName1"); - fail("addSubscriptionsIntoGroup should fail with no permission on sub 2."); - } catch (SecurityException e) { - // Expected result. - } - - doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(1); - doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); - // Try to add sub 2 into group of sub 1. - // Should fail as it doesn't have carrier privilege on sub 1. - try { - mSubscriptionControllerUT.addSubscriptionsIntoGroup( - new int[] {2}, groupId, "packageName2"); - fail("addSubscriptionsIntoGroup should fail with no permission on the group (sub 1)."); - } catch (SecurityException e) { - // Expected result. - } - - doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); - mSubscriptionControllerUT.addSubscriptionsIntoGroup(new int[] {2}, groupId, "packageName2"); - List infoList = mSubscriptionControllerUT - .getSubscriptionsInGroup(groupId, "packageName2", "feature2"); - assertEquals(2, infoList.size()); - } - - @Test - @SmallTest - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testUpdateSubscriptionGroupWithCarrierPrivilegePermission() throws Exception { - testInsertSim(); - // Adding a second profile and mark as embedded. - // TODO b/123300875 slot index 1 is not expected to be valid - mSubscriptionControllerUT.addSubInfo("test2", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, 1); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null); - mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); - - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - - int[] subIdList = new int[] {1}; - - doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList(); - doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); - doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); - - ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, "packageName1"); - assertNotEquals(null, groupId); - - mSubscriptionControllerUT.addSubscriptionsIntoGroup( - new int[] {2}, groupId, "packageName1"); - List infoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupId, "packageName1", "feature1"); - assertEquals(2, infoList.size()); - assertEquals(1, infoList.get(0).getSubscriptionId()); - assertEquals(2, infoList.get(1).getSubscriptionId()); - - mSubscriptionControllerUT.removeSubscriptionsFromGroup( - new int[] {2}, groupId, "packageName1"); - infoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupId, "packageName1", "feature1"); - assertEquals(1, infoList.size()); - assertEquals(1, infoList.get(0).getSubscriptionId()); - - // Make sub 1 inactive. - mSubscriptionControllerUT.clearSubInfoRecord(0); - - try { - mSubscriptionControllerUT.addSubscriptionsIntoGroup( - new int[] {2}, groupId, "packageName2"); - fail("addSubscriptionsIntoGroup should fail with wrong callingPackage name"); - } catch (SecurityException e) { - // Expected result. - } - - // Adding and removing subscription should still work for packageName1, as it's the group - // owner who created the group earlier.. - mSubscriptionControllerUT.addSubscriptionsIntoGroup( - new int[] {2}, groupId, "packageName1"); - infoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupId, "packageName1", "feature1"); - assertEquals(2, infoList.size()); - assertEquals(1, infoList.get(0).getSubscriptionId()); - assertEquals(2, infoList.get(1).getSubscriptionId()); - - mSubscriptionControllerUT.removeSubscriptionsFromGroup( - new int[] {2}, groupId, "packageName1"); - infoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupId, "packageName1", "feature1"); - assertEquals(1, infoList.size()); - assertEquals(1, infoList.get(0).getSubscriptionId()); - } - - @Test - @SmallTest - public void testDisabledSubscriptionGroup() throws Exception { - registerMockTelephonyRegistry(); - - testInsertSim(); - // Adding a second profile and mark as embedded. - mSubscriptionControllerUT.addSubInfo("test2", null, 0, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, 1); - values.put(SubscriptionManager.IS_OPPORTUNISTIC, 1); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null); - mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); - mSubscriptionControllerUT.notifySubscriptionInfoChanged(); - - verify(mTelephonyRegistryManager, times(1)) - .notifyOpportunisticSubscriptionInfoChanged(); - - // Set sub 1 and 2 into same group. - int[] subIdList = new int[] {1, 2}; - ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - assertNotEquals(null, groupId); - - mSubscriptionControllerUT.notifySubscriptionInfoChanged(); - verify(mTelephonyRegistryManager, times(2)) - .notifyOpportunisticSubscriptionInfoChanged(); - List opptSubList = mSubscriptionControllerUT - .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature); - assertEquals(1, opptSubList.size()); - assertEquals(2, opptSubList.get(0).getSubscriptionId()); - assertEquals(false, opptSubList.get(0).isGroupDisabled()); - - // Unplug SIM 1. This should trigger subscription controller disabling sub 2. - values = new ContentValues(); - values.put(SubscriptionManager.SIM_SLOT_INDEX, -1); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 1, null); - mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); - mSubscriptionControllerUT.notifySubscriptionInfoChanged(); - - verify(mTelephonyRegistryManager, times(3)) - .notifyOpportunisticSubscriptionInfoChanged(); - opptSubList = mSubscriptionControllerUT.getOpportunisticSubscriptions(mCallingPackage, - mCallingFeature); - assertEquals(1, opptSubList.size()); - assertEquals(2, opptSubList.get(0).getSubscriptionId()); - assertEquals(true, opptSubList.get(0).isGroupDisabled()); - } - - @Test - @SmallTest - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testSetSubscriptionGroup() throws Exception { - testInsertSim(); - // Adding a second profile and mark as embedded. - mSubscriptionControllerUT.addSubInfo("test2", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - ContentValues values = new ContentValues(); - values.put(SubscriptionManager.IS_EMBEDDED, 1); - mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, - SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null); - mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); - - assertTrue(mSubscriptionControllerUT.isActiveSubId(1)); - assertTrue(mSubscriptionControllerUT.isActiveSubId(2)); - assertTrue(TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, 1, - mContext.getOpPackageName(), mContext.getAttributionTag(), - "getSubscriptionsInGroup")); - - int[] subIdList = new int[] {1}; - ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup( - subIdList, mContext.getOpPackageName()); - assertNotEquals(null, groupUuid); - - // Sub 1 and sub 2 should be in same group. - doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList(); - List infoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag()); - assertNotEquals(null, infoList); - assertEquals(1, infoList.size()); - assertEquals(1, infoList.get(0).getSubscriptionId()); - - subIdList = new int[] {2}; - - mSubscriptionControllerUT.addSubscriptionsIntoGroup( - subIdList, groupUuid, mContext.getOpPackageName()); - infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, - mContext.getOpPackageName(), mContext.getAttributionTag()); - assertEquals(2, infoList.size()); - assertEquals(2, infoList.get(1).getSubscriptionId()); - - // Remove group of sub 1. - subIdList = new int[] {1}; - mSubscriptionControllerUT.removeSubscriptionsFromGroup( - subIdList, groupUuid, mContext.getOpPackageName()); - infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, - mContext.getOpPackageName(), mContext.getAttributionTag()); - assertEquals(1, infoList.size()); - assertEquals(2, infoList.get(0).getSubscriptionId()); - - // Adding sub 1 into a non-existing UUID, which should be granted. - groupUuid = new ParcelUuid(UUID.randomUUID()); - mSubscriptionControllerUT.addSubscriptionsIntoGroup( - subIdList, groupUuid, mContext.getOpPackageName()); - infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, - mContext.getOpPackageName(), mContext.getAttributionTag()); - assertEquals(1, infoList.size()); - assertEquals(1, infoList.get(0).getSubscriptionId()); - } - - private void registerMockTelephonyRegistry() { - mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegistryMock); - doReturn(mTelephonyRegistryMock).when(mTelephonyRegistryMock) - .queryLocalInterface(anyString()); - } - - @Test - @SmallTest - public void testEnableDisableSubscriptionSanity() throws Exception { - testInsertSim(); - - // Non existing subId. - assertFalse(mSubscriptionControllerUT.isSubscriptionEnabled(2)); - - // Test invalid arguments. - try { - assertFalse(mSubscriptionControllerUT.isSubscriptionEnabled(-1)); - fail("Should throw IllegalArgumentException with invalid subId."); - } catch (IllegalArgumentException exception) { - // Expected. - } - - try { - mSubscriptionControllerUT.getEnabledSubscriptionId(3); - fail("Should throw IllegalArgumentException with invalid subId."); - } catch (IllegalArgumentException exception) { - // Expected. - } - } - - @Test - @SmallTest - public void testGetActiveSubIdList() throws Exception { - // TODO b/123300875 slot index 1 is not expected to be valid - mSubscriptionControllerUT.addSubInfo("123", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 1 - mSubscriptionControllerUT.addSubInfo("456", null, 0, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 2 - - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - // Make sure the return sub ids are sorted by slot index - assertTrue("active sub ids = " + Arrays.toString(subIds), - Arrays.equals(subIds, new int[]{2, 1})); - } - - @Test - public void testGetActiveSubscriptionInfoWithNoPermissions() throws Exception { - // If the calling package does not have the READ_PHONE_STATE permission or carrier - // privileges then getActiveSubscriptionInfo should throw a SecurityException; - testInsertSim(); - setupReadPrivilegePermission(); - int subId = getFirstSubId(); - removeReadPrivilegePermission(); - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - - try { - mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage, - mCallingFeature); - fail("getActiveSubscriptionInfo should fail when invoked with no permissions"); - } catch (SecurityException expected) { - } - } - - @Test - public void testGetActiveSubscriptionInfoWithReadPhoneState() throws Exception { - // If the calling package only has the READ_PHONE_STATE permission then - // getActiveSubscriptionInfo should still return a result but the ICC ID should not be - // available via getIccId or getCardString. - testInsertSim(); - setupReadPhoneNumbersTest(); - setIdentifierAccess(false); - setupReadPrivilegePermission(); - int subId = getFirstSubId(); - - SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( - subId, mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId()); - assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString()); - assertEquals(UNAVAILABLE_NUMBER, subscriptionInfo.getNumber()); - } - - @Test - public void testGetActiveSubscriptionWithPhoneNumberAccess() throws Exception { - // If the calling package meets any of the requirements for the - // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available - // in the SubscriptionInfo. - testInsertSim(); - setupReadPhoneNumbersTest(); - setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED); - setupReadPrivilegePermission(); - int subId = getFirstSubId(); - - SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( - subId, mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertEquals(DISPLAY_NUMBER, subscriptionInfo.getNumber()); - } - - @Test - public void testGetActiveSubscriptionInfoWithCarrierPrivileges() throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo. - testInsertSim(); - setupIdentifierCarrierPrivilegesTest(); - setupReadPrivilegePermission(); - int subId = getFirstSubId(); - - SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( - subId, mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertTrue(subscriptionInfo.getIccId().length() > 0); - assertTrue(subscriptionInfo.getCardString().length() > 0); - } - - @Test - public void testGetActiveSubscriptionWithPrivilegedPermission() throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo. - testInsertSim(); - int subId = getFirstSubId(); - - SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( - subId, mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertTrue(subscriptionInfo.getIccId().length() > 0); - assertTrue(subscriptionInfo.getCardString().length() > 0); - } - - @Test - public void testGetActiveSubscriptionInfoForSimSlotIndexWithNoPermission() throws Exception { - // If the calling package does not have the READ_PHONE_STATE permission or carrier - // privileges then getActiveSubscriptionInfoForSimSlotIndex should throw a - // SecurityException. - testInsertSim(); - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - - try { - mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage, - mCallingFeature); - fail("getActiveSubscriptionInfoForSimSlotIndex should fail when invoked with no " - + "permissions"); - } catch (SecurityException expected) { - } - } - - @Test - public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneState() throws Exception { - // If the calling package only has the READ_PHONE_STATE permission then - // getActiveSubscriptionInfoForSimlSlotIndex should still return the SubscriptionInfo but - // the ICC ID should not be available via getIccId or getCardString. - testInsertSim(); - setupReadPhoneNumbersTest(); - setIdentifierAccess(false); - - SubscriptionInfo subscriptionInfo = - mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, - mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId()); - assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString()); - assertEquals(UNAVAILABLE_NUMBER, subscriptionInfo.getNumber()); - } - - @Test - public void testGetActiveSubscriptionInfoForSimSlotIndexWithPhoneNumberAccess() - throws Exception { - // If the calling package meets any of the requirements for the - // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available - // in the SubscriptionInfo. - testInsertSim(); - setupReadPhoneNumbersTest(); - setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED); - - SubscriptionInfo subscriptionInfo = - mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, - mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertEquals(DISPLAY_NUMBER, subscriptionInfo.getNumber()); - } - - @Test - public void testGetActiveSubscriptionInfoForSimSlotIndexWithCarrierPrivileges() - throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo. - testInsertSim(); - setupIdentifierCarrierPrivilegesTest(); - - SubscriptionInfo subscriptionInfo = - mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, - mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertTrue(subscriptionInfo.getIccId().length() > 0); - assertTrue(subscriptionInfo.getCardString().length() > 0); - } - - @Test - public void testGetActiveSubscriptionInfoForSimSlotIndexWithPrivilegedPermission() - throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo. - testInsertSim(); - - SubscriptionInfo subscriptionInfo = - mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, - mCallingPackage, mCallingFeature); - - assertNotNull(subscriptionInfo); - assertTrue(subscriptionInfo.getIccId().length() > 0); - assertTrue(subscriptionInfo.getCardString().length() > 0); - } - - @Test - public void testGetActiveSubscriptionInfoListWithNoPermission() throws Exception { - // If the calling package does not have the READ_PHONE_STATE permission or carrier - // privileges then getActiveSubscriptionInfoList should return a list with 0 elements. - testInsertSim(); - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertNotNull(subInfoList); - assertTrue(subInfoList.size() == 0); - } - - @Test - public void testGetActiveSubscriptionInfoListWithReadPhoneState() throws Exception { - // If the calling package only has the READ_PHONE_STATE permission then - // getActiveSubscriptionInfoList should still return the list of SubscriptionInfo objects - // but the ICC ID should not be available via getIccId or getCardString. - testInsertSim(); - setupReadPhoneNumbersTest(); - setIdentifierAccess(false); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertTrue(subInfoList.size() > 0); - for (SubscriptionInfo info : subInfoList) { - assertEquals(UNAVAILABLE_ICCID, info.getIccId()); - assertEquals(UNAVAILABLE_ICCID, info.getCardString()); - assertEquals(UNAVAILABLE_NUMBER, info.getNumber()); - } - } - - @Test - public void testGetActiveSubscriptionInfoListWithPhoneNumberAccess() throws Exception { - // If the calling package meets any of the requirements for the - // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available - // in the SubscriptionInfo. - testInsertSim(); - setupReadPhoneNumbersTest(); - setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertTrue(subInfoList.size() > 0); - SubscriptionInfo subInfo = subInfoList.get(0); - assertEquals(DISPLAY_NUMBER, subInfo.getNumber()); - } - - @Test - public void testGetActiveSubscriptionInfoListWithCarrierPrivileges() throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. - testInsertSim(); - setupIdentifierCarrierPrivilegesTest(); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertTrue(subInfoList.size() > 0); - for (SubscriptionInfo info : subInfoList) { - assertTrue(info.getIccId().length() > 0); - assertTrue(info.getCardString().length() > 0); - } - } - - @Test - public void testGetActiveSubscriptionInfoListWithCarrierPrivilegesOnOneSubId() - throws Exception { - // If an app does not have the READ_PHONE_STATE permission but has carrier privileges on one - // out of multiple sub IDs then the SubscriptionInfo for that subId should be returned with - // the ICC ID and phone number. - testInsertSim(); - doReturn(2).when(mTelephonyManager).getPhoneCount(); - mSubscriptionControllerUT.addSubInfo("test2", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - int firstSubId = getFirstSubId(); - int secondSubId = getSubIdAtIndex(1); - mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, secondSubId); - setupIdentifierCarrierPrivilegesTest(); - mContextFixture.removeCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - setCarrierPrivilegesForSubId(false, firstSubId); - setCarrierPrivilegesForSubId(true, secondSubId); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertEquals(1, subInfoList.size()); - SubscriptionInfo subInfo = subInfoList.get(0); - assertEquals("test2", subInfo.getIccId()); - assertEquals(DISPLAY_NUMBER, subInfo.getNumber()); - } - - @Test - public void testGetActiveSubscriptionInfoListCheckOrder() - throws Exception { - // If an app does not have the READ_PHONE_STATE permission but has carrier privileges on one - // out of multiple sub IDs then the SubscriptionInfo for that subId should be returned with - // the ICC ID and phone number. - testInsertSim(); - doReturn(2).when(mTelephonyManager).getPhoneCount(); - mSubscriptionControllerUT.addSubInfo("test2", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - int firstSubId = getFirstSubId(); - int secondSubId = getSubIdAtIndex(1); - setupIdentifierCarrierPrivilegesTest(); - setCarrierPrivilegesForSubId(false, firstSubId); - setCarrierPrivilegesForSubId(true, secondSubId); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertEquals(2, subInfoList.size()); - assertTrue(subInfoList.get(0).getSubscriptionId() < subInfoList.get(1).getSubscriptionId()); - } - - @Test - public void testGetActiveSubscriptionInfoListWithIdentifierAccessWithoutNumberAccess() - throws Exception { - // An app with access to device identifiers may not have access to the device phone number - // (ie an app that passes the device / profile owner check or an app that has been granted - // the device identifiers appop); this test verifies that an app with identifier access - // can read the ICC ID but does not receive the phone number. - testInsertSim(); - setupReadPhoneNumbersTest(); - setIdentifierAccess(true); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertEquals(1, subInfoList.size()); - SubscriptionInfo subInfo = subInfoList.get(0); - assertEquals("test", subInfo.getIccId()); - assertEquals(UNAVAILABLE_NUMBER, subInfo.getNumber()); - } - - @Test - public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. - testInsertSim(); - - List subInfoList = - mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, - mCallingFeature); - - assertTrue(subInfoList.size() > 0); - for (SubscriptionInfo info : subInfoList) { - assertTrue(info.getIccId().length() > 0); - assertTrue(info.getCardString().length() > 0); - } - } - - @Test - @DisableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testGetSubscriptionsInGroupWithReadPhoneState() throws Exception { - // For backward compatibility test - ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); - setupReadPhoneNumbersTest(); - setIdentifierAccess(false); - - List subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupUuid, mCallingPackage, mCallingFeature); - - assertTrue(subInfoList.size() > 0); - for (SubscriptionInfo info : subInfoList) { - assertEquals(UNAVAILABLE_ICCID, info.getIccId()); - assertEquals(UNAVAILABLE_ICCID, info.getCardString()); - assertEquals(UNAVAILABLE_NUMBER, info.getNumber()); - } - } - - @Test - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testGetSubscriptionsInGroupWithoutAppropriatePermission() throws Exception { - ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); - - // no permission - setNoPermission(); - try { - mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage, - mCallingFeature); - fail("getSubscriptionsInGroup should fail when invoked with no permissions"); - } catch (SecurityException expected) { - } - - // only has the device identifiers permission - setIdentifierAccess(true); - try { - mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage, - mCallingFeature); - fail("getSubscriptionsInGroup should fail when invoked with no" - + "READ_PHONE_STATE permissions"); - } catch (SecurityException expected) { - } - - // only has the READ_PHONE_STATE permission - setIdentifierAccess(false); - setReadPhoneState(); - List subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupUuid, mCallingPackage, mCallingFeature); - assertNotNull(subInfoList); - assertTrue(subInfoList.isEmpty()); - } - - @Test - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testGetSubscriptionsInGroupWithReadDeviceIdentifier() throws Exception { - ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); - setNoPermission(); - setCarrierPrivileges(false); - setIdentifierAccess(true); - setReadPhoneState(); - - List subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupUuid, mCallingPackage, mCallingFeature); - - assertTrue(subInfoList.size() > 0); - for (SubscriptionInfo info : subInfoList) { - assertTrue(info.getIccId().length() > 0); - assertTrue(info.getCardString().length() > 0); - } - } - - private void setNoPermission() { - doThrow(new SecurityException()).when(mContext) - .enforcePermission(anyString(), anyInt(), anyInt(), anyString()); - } - - private void setReadPhoneState() { - doNothing().when(mContext).enforcePermission( - eq(android.Manifest.permission.READ_PHONE_STATE), anyInt(), anyInt(), anyString()); - } - - @Test - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testGetSubscriptionInGroupWithPhoneNumberAccess() throws Exception { - // If the calling package meets any of the requirements for the - // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available - // in the SubscriptionInfo. - ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); - setupReadPhoneNumbersTest(); - setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED); - - List subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupUuid, mCallingPackage, mCallingFeature); - - assertTrue(subInfoList.size() > 0); - SubscriptionInfo subInfo = subInfoList.get(0); - assertEquals(DISPLAY_NUMBER, subInfo.getNumber()); - } - - @Test - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testGetSubscriptionsInGroupWithCarrierPrivileges() throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. - ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); - setupIdentifierCarrierPrivilegesTest(); - - List subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupUuid, mCallingPackage, mCallingFeature); - - assertTrue(subInfoList.size() > 0); - for (SubscriptionInfo info : subInfoList) { - assertTrue(info.getIccId().length() > 0); - assertTrue(info.getCardString().length() > 0); - } - } - - @Test - @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID}) - public void testGetSubscriptionsInGroupWithPrivilegedPermission() throws Exception { - // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier - // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. - ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); - - List subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( - groupUuid, mCallingPackage, mCallingFeature); - - assertTrue(subInfoList.size() > 0); - for (SubscriptionInfo info : subInfoList) { - assertTrue(info.getIccId().length() > 0); - assertTrue(info.getCardString().length() > 0); - } - } - - private ParcelUuid setupGetSubscriptionsInGroupTest() throws Exception { - testInsertSim(); - int[] subIdList = new int[]{getFirstSubId()}; - ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(subIdList, - mCallingPackage); - assertNotNull(groupUuid); - doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList(); - return groupUuid; - } - - private void setupReadPhoneNumbersTest() throws Exception { - mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, getFirstSubId()); - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - setupMocksForTelephonyPermissions(Build.VERSION_CODES.R); - setPhoneNumberAccess(PackageManager.PERMISSION_DENIED); - } - - private void setupIdentifierCarrierPrivilegesTest() throws Exception { - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); - setupMocksForTelephonyPermissions(); - setIdentifierAccess(false); - setCarrierPrivileges(true); - } - - private void setupReadPrivilegePermission() throws Exception { - mContextFixture.addCallingOrSelfPermissionToCurrentPermissions( - Manifest.permission.READ_PRIVILEGED_PHONE_STATE); - } - private void removeReadPrivilegePermission() throws Exception { - mContextFixture.removeCallingOrSelfPermission( - Manifest.permission.READ_PRIVILEGED_PHONE_STATE); - } - - private int getFirstSubId() throws Exception { - return getSubIdAtIndex(0); - } - - private int getSubIdAtIndex(int index) throws Exception { - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibileOnly*/false); - assertTrue(subIds != null && subIds.length > index); - return subIds[index]; - } - - @Test - public void testGetEnabledSubscriptionIdSingleSIM() { - // A single SIM device may have logical slot 0 mapped to physical slot 1 - // (i.e. logical slot -1 mapped to physical slot 0) - UiccSlotInfo slot0 = getFakeUiccSlotInfo(false, -1, null); - UiccSlotInfo slot1 = getFakeUiccSlotInfo(true, 0, null); - UiccSlotInfo [] uiccSlotInfos = {slot0, slot1}; - UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot}; - - doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo(); - doReturn(uiccSlots).when(mUiccController).getUiccSlots(); - assertEquals(2, UiccController.getInstance().getUiccSlots().length); - - ContentResolver resolver = mContext.getContentResolver(); - // logical 0 should find physical 1, has settings enabled subscription 0 - Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 1, 0); - - int enabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(0); - assertEquals(0, enabledSubscription); - } - - @Test - public void testGetEnabledSubscriptionIdDualSIM() { - doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount(); - doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount(); - doReturn(SINGLE_SIM).when(mTelephonyManager).getActiveModemCount(); - // A dual SIM device may have logical slot 0 mapped to physical slot 0 - // (i.e. logical slot 1 mapped to physical slot 1) - UiccSlotInfo slot0 = getFakeUiccSlotInfo(true, 0, null); - UiccSlotInfo slot1 = getFakeUiccSlotInfo(true, 1, null); - UiccSlotInfo [] uiccSlotInfos = {slot0, slot1}; - UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot}; - - doReturn(2).when(mTelephonyManager).getPhoneCount(); - doReturn(2).when(mTelephonyManager).getActiveModemCount(); - doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo(); - doReturn(uiccSlots).when(mUiccController).getUiccSlots(); - assertEquals(2, UiccController.getInstance().getUiccSlots().length); - - ContentResolver resolver = mContext.getContentResolver(); - // logical 0 should find physical 0, has settings enabled subscription 0 - Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 0, 0); - Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 1, 1); - - int enabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(0); - int secondEabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(1); - assertEquals(0, enabledSubscription); - assertEquals(1, secondEabledSubscription); - } - - - private UiccSlotInfo getFakeUiccSlotInfo(boolean active, int logicalSlotIndex, String iccId) { - return getFakeUiccSlotInfo(active, logicalSlotIndex, "fake card Id", iccId); - } - - private UiccSlotInfo getFakeUiccSlotInfo( - boolean active, int logicalSlotIndex, String cardId, String iccId) { - return new UiccSlotInfo(false, cardId, - UiccSlotInfo.CARD_STATE_INFO_PRESENT, true, true, - Collections.singletonList( - new UiccPortInfo(iccId, 0, logicalSlotIndex, active) - )); - } - - @Test - @SmallTest - public void testNameSourcePriority() throws Exception { - assertTrue(mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_USER_INPUT) - > mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_CARRIER)); - - assertTrue(mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_CARRIER) - > mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_SIM_SPN)); - - assertTrue(mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_SIM_SPN) - > mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_SIM_PNN)); - - assertTrue(mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_SIM_PNN) - > mSubscriptionControllerUT.getNameSourcePriority( - SubscriptionManager.NAME_SOURCE_CARRIER_ID)); - } - - @Test - @SmallTest - public void testGetAvailableSubscriptionList() throws Exception { - // TODO b/123300875 slot index 1 is not expected to be valid - mSubscriptionControllerUT.addSubInfo("123", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 1 - mSubscriptionControllerUT.addSubInfo("456", null, 0, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 2 - - List infoList = mSubscriptionControllerUT - .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature); - assertEquals(2, infoList.size()); - assertEquals("456", infoList.get(0).getIccId()); - assertEquals("123", infoList.get(1).getIccId()); - - // Remove "123" from active sim list but have it inserted. - UiccSlot[] uiccSlots = {mUiccSlot}; - IccCardStatus.CardState cardState = CARDSTATE_PRESENT; - doReturn(uiccSlots).when(mUiccController).getUiccSlots(); - doReturn(cardState).when(mUiccSlot).getCardState(); - doReturn("123").when(mUiccSlot).getIccId(0); // default port index - mSubscriptionControllerUT.clearSubInfoRecord(1); - - // Active sub list should return 1 now. - infoList = mSubscriptionControllerUT - .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature); - assertEquals(1, infoList.size()); - assertEquals("456", infoList.get(0).getIccId()); - - // Available sub list should still return two. - infoList = mSubscriptionControllerUT - .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature); - assertEquals(2, infoList.size()); - assertEquals("123", infoList.get(0).getIccId()); - assertEquals("456", infoList.get(1).getIccId()); - } - - @Test - @SmallTest - public void testGetAvailableSubscriptionList_withTrailingF() throws Exception { - // TODO b/123300875 slot index 1 is not expected to be valid - mSubscriptionControllerUT.addSubInfo("123", null, 1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 1 - mSubscriptionControllerUT.addSubInfo("456", null, 0, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 2 - - // Remove "123" from active sim list but have it inserted. - UiccSlot[] uiccSlots = {mUiccSlot}; - IccCardStatus.CardState cardState = CARDSTATE_PRESENT; - doReturn(uiccSlots).when(mUiccController).getUiccSlots(); - doReturn(cardState).when(mUiccSlot).getCardState(); - // IccId ends with a 'F' which should be ignored and taking into account. - doReturn("123F").when(mUiccSlot).getIccId(0); // default port index - - mSubscriptionControllerUT.clearSubInfoRecord(1); - - // Active sub list should return 1 now. - List infoList = mSubscriptionControllerUT - .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature); - assertEquals(1, infoList.size()); - assertEquals("456", infoList.get(0).getIccId()); - - // Available sub list should still return two. - infoList = mSubscriptionControllerUT - .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature); - assertEquals(2, infoList.size()); - assertEquals("123", infoList.get(0).getIccId()); - assertEquals("456", infoList.get(1).getIccId()); - } - - @Test - @SmallTest - public void testSetPreferredDataSubscriptionId_phoneSwitcherNotInitialized() throws Exception { - replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, null); - - mSubscriptionControllerUT.setPreferredDataSubscriptionId(1, true, mSetOpptDataCallback); - verify(mSetOpptDataCallback).onComplete(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION); - } - - @Test - @SmallTest - public void testGetPreferredDataSubscriptionId_phoneSwitcherNotInitialized() throws Exception { - replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, null); - - assertEquals(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, - mSubscriptionControllerUT.getPreferredDataSubscriptionId()); - } - - @Test - public void testSetSubscriptionEnabled_disableActivePsim_cardIdWithTrailingF() { - String iccId = "123F"; - mSubscriptionControllerUT.addSubInfo(iccId, null, 0, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - mSubscriptionControllerUT.registerForUiccAppsEnabled(mHandler, 0, null, false); - UiccSlotInfo slot = getFakeUiccSlotInfo(true, 0, iccId + "FF", iccId); - UiccSlotInfo[] uiccSlotInfos = {slot}; - doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo(); - - mSubscriptionControllerUT.setSubscriptionEnabled(false, 1); - verify(mHandler).sendMessageAtTime(any(), anyLong()); - assertFalse(mSubscriptionControllerUT.getActiveSubscriptionInfo( - 1, mContext.getOpPackageName(), null).areUiccApplicationsEnabled()); - } - - @Test - @SmallTest - public void testInsertEmptySubInfoRecord_returnsNull_ifRecordExists() { - final String mockedIccid = "123456789"; - final int mockedSlotIndex = 1; - - assertNotNull(mSubscriptionControllerUT.insertEmptySubInfoRecord( - mockedIccid, mockedSlotIndex)); - // Insert second time with the same iccid should result in no-op and return null. - assertNull(mSubscriptionControllerUT.insertEmptySubInfoRecord( - mockedIccid, mockedSlotIndex)); - assertEquals( - 1, - mSubscriptionControllerUT - .getAllSubInfoList(mCallingPackage, mCallingFeature).size()); - } - - @Test - @SmallTest - public void testCheckPhoneIdAndIccIdMatch() { - try { - testSetSubscriptionGroupWithModifyPermission(); - } catch (Exception e) { - fail("Unexpected exception: " + e); - } - - mSubscriptionControllerUT.addSubInfo("test3", null, - SubscriptionManager.INVALID_SIM_SLOT_INDEX, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); - - assertTrue(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test")); - assertTrue(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test2")); - assertFalse(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test3")); - } - - @Test - @SmallTest - public void testMessageRefDBFetchAndUpdate() throws Exception { - testInsertSim(); - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - SubscriptionController.getInstance().updateMessageRef(subId, 201); - int messageRef = SubscriptionController.getInstance().getMessageRef(subId); - assertTrue("201 :", messageRef == 201); - } - - @Test - public void setSubscriptionUserHandle_withoutPermission() { - testInsertSim(); - enableGetSubscriptionUserHandle(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - - assertThrows(SecurityException.class, - () -> mSubscriptionControllerUT.setSubscriptionUserHandle( - UserHandle.of(UserHandle.USER_SYSTEM), subId)); - } - - @Test - public void setGetSubscriptionUserHandle_userHandleNull() { - testInsertSim(); - enableGetSubscriptionUserHandle(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - - mSubscriptionControllerUT.setSubscriptionUserHandle(null, subId); - - assertThat(mSubscriptionControllerUT.getSubscriptionUserHandle(subId)) - .isEqualTo(null); - } - - @Test - public void setSubscriptionUserHandle_invalidSubId() { - enableGetSubscriptionUserHandle(); - - assertThrows(IllegalArgumentException.class, - () -> mSubscriptionControllerUT.setSubscriptionUserHandle( - UserHandle.of(UserHandle.USER_SYSTEM), - SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)); - } - - @Test - public void setGetSubscriptionUserHandle_withValidUserHandleAndSubId() { - testInsertSim(); - enableGetSubscriptionUserHandle(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - - mSubscriptionControllerUT.setSubscriptionUserHandle( - UserHandle.of(UserHandle.USER_SYSTEM), subId); - - assertThat(mSubscriptionControllerUT.getSubscriptionUserHandle(subId)) - .isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM)); - } - - @Test - public void getSubscriptionUserHandle_withoutPermission() { - testInsertSim(); - enableGetSubscriptionUserHandle(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - - assertThrows(SecurityException.class, - () -> mSubscriptionControllerUT.getSubscriptionUserHandle(subId)); - } - - @Test - public void getSubscriptionUserHandle_invalidSubId() { - enableGetSubscriptionUserHandle(); - - assertThrows(IllegalArgumentException.class, - () -> mSubscriptionControllerUT.getSubscriptionUserHandle( - SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)); - } - - @Test - public void isSubscriptionAssociatedWithUser_withoutPermission() { - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - - assertThrows(SecurityException.class, - () -> mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(1, - UserHandle.of(UserHandle.USER_SYSTEM))); - } - - @Test - public void isSubscriptionAssociatedWithUser_noSubscription() { - // isSubscriptionAssociatedWithUser should return true if there are no active subscriptions. - assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(1, - UserHandle.of(UserHandle.USER_SYSTEM))).isEqualTo(true); - } - - @Test - public void isSubscriptionAssociatedWithUser_unknownSubId() { - testInsertSim(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - int unknownSubId = 123; - - assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(unknownSubId, - UserHandle.of(UserHandle.USER_SYSTEM))).isEqualTo(false); - } - - @Test - public void isSubscriptionAssociatedWithUser_userAssociatedWithSubscription() { - testInsertSim(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - - mSubscriptionControllerUT.setSubscriptionUserHandle( - UserHandle.of(UserHandle.USER_SYSTEM), subId); - - assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(subId, - UserHandle.of(UserHandle.USER_SYSTEM))).isEqualTo(true); - } - - @Test - public void isSubscriptionAssociatedWithUser_userNotAssociatedWithSubscription() { - testInsertSim(); - enableGetSubscriptionUserHandle(); - /* Get SUB ID */ - int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); - assertTrue(subIds != null && subIds.length != 0); - final int subId = subIds[0]; - - mSubscriptionControllerUT.setSubscriptionUserHandle(UserHandle.of(UserHandle.USER_SYSTEM), - subId); - - assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(subId, - UserHandle.of(10))).isEqualTo(false); - } - - - @Test - public void getSubscriptionInfoListAssociatedWithUser_withoutPermission() { - mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); - - assertThrows(SecurityException.class, - () -> mSubscriptionControllerUT.getSubscriptionInfoListAssociatedWithUser( - UserHandle.of(UserHandle.USER_SYSTEM))); - } - - @Test - public void getSubscriptionInfoListAssociatedWithUser_noSubscription() { - List associatedSubInfoList = mSubscriptionControllerUT - .getSubscriptionInfoListAssociatedWithUser(UserHandle.of(UserHandle.USER_SYSTEM)); - assertThat(associatedSubInfoList.size()).isEqualTo(0); - } - - private void enableGetSubscriptionUserHandle() { - Resources mResources = mock(Resources.class); - doReturn(true).when(mResources).getBoolean( - eq(com.android.internal.R.bool.config_enable_get_subscription_user_handle)); - doReturn(mResources).when(mContext).getResources(); - } -} diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java deleted file mode 100644 index 32fb56ecb171797a85995987d409969f94c78bc5..0000000000000000000000000000000000000000 --- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java +++ /dev/null @@ -1,1123 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import static android.telephony.SubscriptionManager.UICC_APPLICATIONS_ENABLED; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; -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 static org.mockito.Mockito.when; - -import android.annotation.Nullable; -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.IPackageManager; -import android.content.pm.UserInfo; -import android.net.Uri; -import android.os.Bundle; -import android.os.Looper; -import android.os.ParcelUuid; -import android.os.PersistableBundle; -import android.service.euicc.EuiccProfileInfo; -import android.service.euicc.EuiccService; -import android.service.euicc.GetEuiccProfileInfoListResult; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.UiccAccessRule; -import android.test.mock.MockContentProvider; -import android.test.mock.MockContentResolver; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import com.android.internal.telephony.euicc.EuiccController; -import com.android.internal.telephony.uicc.IccFileHandler; -import com.android.internal.telephony.uicc.IccRecords; -import com.android.internal.telephony.uicc.IccUtils; -import com.android.internal.telephony.uicc.UiccSlot; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class SubscriptionInfoUpdaterTest extends TelephonyTest { - private static final int FAKE_SUB_ID_1 = 0; - private static final int FAKE_SUB_ID_2 = 1; - private static final int FAKE_CARD_ID = 0; - private static final String FAKE_EID = "89049032000001000000031328322874"; - private static final String FAKE_ICCID_1 = "89012604200000000000"; - private static final String FAKE_MCC_MNC_1 = "123456"; - private static final String FAKE_MCC_MNC_2 = "456789"; - private static final int FAKE_PHONE_ID_1 = 0; - - private SubscriptionInfoUpdater mUpdater; - private IccRecords mIccRecord; - - // Mocked classes - private UserInfo mUserInfo; - private SubscriptionInfo mSubInfo; - private ContentProvider mContentProvider; - private HashMap mSubscriptionContent; - private IccFileHandler mIccFileHandler; - private EuiccController mEuiccController; - private IntentBroadcaster mIntentBroadcaster; - private IPackageManager mPackageManager; - private UiccSlot mUiccSlot; - - /*Custom ContentProvider */ - private class FakeSubscriptionContentProvider extends MockContentProvider { - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - return mContentProvider.update(uri, values, selection, selectionArgs); - } - - @Override - public Bundle call(String method, @Nullable String args, @Nullable Bundle bundle) { - return new Bundle(); - } - } - - @Before - public void setUp() throws Exception { - super.setUp(getClass().getSimpleName()); - enableSubscriptionManagerService(false); - mUserInfo = mock(UserInfo.class); - mSubInfo = mock(SubscriptionInfo.class); - mContentProvider = mock(ContentProvider.class); - mSubscriptionContent = mock(HashMap.class); - mIccFileHandler = mock(IccFileHandler.class); - mEuiccController = mock(EuiccController.class); - mIntentBroadcaster = mock(IntentBroadcaster.class); - mPackageManager = mock(IPackageManager.class); - mUiccSlot = mock(UiccSlot.class); - - replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null, new String[1]); - replaceInstance(SubscriptionInfoUpdater.class, "sContext", null, null); - replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 1); - replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null, new int[1]); - replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null, new int[1]); - replaceInstance(SubscriptionInfoUpdater.class, "sIsSubInfoInitialized", null, false); - - replaceInstance(EuiccController.class, "sInstance", null, mEuiccController); - replaceInstance(IntentBroadcaster.class, "sIntentBroadcaster", null, mIntentBroadcaster); - - doReturn(true).when(mUiccSlot).isActive(); - doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt()); - doReturn(1).when(mTelephonyManager).getSimCount(); - doReturn(1).when(mTelephonyManager).getPhoneCount(); - doReturn(1).when(mTelephonyManager).getActiveModemCount(); - - when(mContentProvider.update(any(), any(), any(), isNull())).thenAnswer( - new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - ContentValues values = invocation.getArgument(1); - for (String key : values.keySet()) { - mSubscriptionContent.put(key, values.get(key)); - } - return 1; - } - }); - - doReturn(mUserInfo).when(mIActivityManager).getCurrentUser(); - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(0); - doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionManager).getActiveSubscriptionIdList(); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - doReturn(new int[]{}).when(mSubscriptionController) - .getActiveSubIdList(/*visibleOnly*/false); - mIccRecord = mUiccProfile.getIccRecords(); - - mUpdater = - new SubscriptionInfoUpdater(Looper.myLooper(), mContext, mSubscriptionController); - processAllMessages(); - - assertFalse(mUpdater.isSubInfoInitialized()); - } - - @After - public void tearDown() throws Exception { - mIccRecord = null; - mUpdater = null; - super.tearDown(); - } - - @Test - @SmallTest - public void testSimAbsent() throws Exception { - doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1)); - doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionController) - .getActiveSubIdList(/*visibleOnly*/false); - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - verify(mSubscriptionController, times(1)).clearSubInfoRecord(eq(FAKE_SUB_ID_1)); - - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_ABSENT)); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testSimAbsentAndInactive() throws Exception { - doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1)); - doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionController) - .getActiveSubIdList(/*visibleOnly*/false); - mUpdater.updateInternalIccStateForInactivePort(FAKE_SUB_ID_1, null); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - verify(mSubscriptionController, times(1)).clearSubInfoRecord(eq(FAKE_SUB_ID_1)); - - // Verify that in the special absent and inactive case, we update subscriptions without - // broadcasting SIM state change - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager, times(0)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_ABSENT)); - verify(mContext, times(0)).sendBroadcast(any(), anyString()); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testSimUnknown() throws Exception { - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, FAKE_SUB_ID_1); - - processAllMessages(); - assertFalse(mUpdater.isSubInfoInitialized()); - verify(mSubscriptionContent, times(0)).put(anyString(), any()); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_UNKNOWN)); - verify(mSubscriptionController, times(0)).clearSubInfo(); - verify(mSubscriptionController, times(0)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testSimNotReady() throws Exception { - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1); - - processAllMessages(); - assertFalse(mUpdater.isSubInfoInitialized()); - verify(mSubscriptionContent, never()).put(anyString(), any()); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager, never()).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY)); - verify(mSubscriptionController, never()).clearSubInfoRecord(FAKE_PHONE_ID_1); - verify(mSubscriptionController, never()).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testSimNotReadyEmptyProfile() throws Exception { - doReturn(mIccCard).when(mPhone).getIccCard(); - doReturn(true).when(mIccCard).isEmptyProfile(); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - // Sub info should be cleared and change should be notified. - verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_PHONE_ID_1)); - verify(mSubscriptionController).notifySubscriptionInfoChanged(); - // No new sub should be added. - verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt()); - verify(mSubscriptionContent, never()).put(anyString(), any()); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY)); - } - - @Test - @SmallTest - public void testSimNotReadyDisabledUiccApps() throws Exception { - String iccId = "123456"; - doReturn(mIccCard).when(mPhone).getIccCard(); - doReturn(false).when(mIccCard).isEmptyProfile(); - doReturn(mUiccPort).when(mUiccController).getUiccPort(anyInt()); - doReturn(iccId).when(mUiccPort).getIccId(); - doReturn(mSubInfo).when(mSubscriptionController).getSubInfoForIccId(iccId); - doReturn(false).when(mSubInfo).areUiccApplicationsEnabled(); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - // Sub info should be cleared and change should be notified. - verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_PHONE_ID_1)); - verify(mSubscriptionController).notifySubscriptionInfoChanged(); - // No new sub should be added. - verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt()); - verify(mSubscriptionContent, never()).put(anyString(), any()); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY)); - - // When becomes ABSENT, UICC_APPLICATIONS_ENABLED should be reset to true. - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_PHONE_ID_1); - processAllMessages(); - ArgumentCaptor valueCapture = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), valueCapture.capture(), - eq(SubscriptionManager.ICC_ID + "=\'" + iccId + "\'"), eq(null)); - ContentValues contentValues = valueCapture.getValue(); - assertTrue(contentValues != null && contentValues.getAsBoolean( - UICC_APPLICATIONS_ENABLED)); - } - - @Test - @SmallTest - public void testSimRemovedWhileDisablingUiccApps() throws Exception { - loadSim(); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1); - processAllMessages(); - - // UICC_APPLICATIONS_ENABLED should be reset to true. - ArgumentCaptor valueCapture = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), valueCapture.capture(), - eq(SubscriptionManager.ICC_ID + "=\'" + FAKE_ICCID_1 + "\'"), eq(null)); - ContentValues contentValues = valueCapture.getValue(); - assertTrue(contentValues != null && contentValues.getAsBoolean( - UICC_APPLICATIONS_ENABLED)); - } - - @Test - @SmallTest - public void testSimError() throws Exception { - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR, null, FAKE_SUB_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - verify(mSubscriptionContent, times(0)).put(anyString(), any()); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR)); - verify(mSubscriptionController, times(0)).clearSubInfo(); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testWrongSimState() throws Exception { - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_IMSI, null, 2); - - processAllMessages(); - assertFalse(mUpdater.isSubInfoInitialized()); - verify(mSubscriptionContent, times(0)).put(anyString(), any()); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager, times(0)).updateConfigForPhoneId(eq(2), - eq(IccCardConstants.INTENT_VALUE_ICC_IMSI)); - verify(mSubscriptionController, times(0)).clearSubInfo(); - verify(mSubscriptionController, times(0)).notifySubscriptionInfoChanged(); - } - - private void loadSim() { - doReturn(FAKE_SUB_ID_1).when(mSubInfo).getSubscriptionId(); - doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1)); - doReturn(FAKE_ICCID_1).when(mIccRecord).getFullIccId(); - doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1); - when(mActivityManager.updateMccMncConfiguration(anyString(), anyString())).thenReturn( - true); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_LOADED)); - } - - @Test - @SmallTest - public void testSimLoaded() throws Exception { - loadSim(); - - // verify SIM_STATE_CHANGED broadcast. It should be broadcast twice, once for - // READ_PHONE_STATE and once for READ_PRIVILEGED_PHONE_STATE - /* todo: cannot verify as intent is sent using ActivityManagerNative.broadcastStickyIntent() - * uncomment code below when that is fixed - */ - /* ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); - ArgumentCaptor stringArgumentCaptor = ArgumentCaptor.forClass(String.class); - verify(mContext, times(2)).sendBroadcast(intentArgumentCaptor.capture(), - stringArgumentCaptor.capture()); - assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED, - intentArgumentCaptor.getAllValues().get(0).getAction()); - assertEquals(Manifest.permission.READ_PHONE_STATE, - stringArgumentCaptor.getAllValues().get(0)); - assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED, - intentArgumentCaptor.getAllValues().get(1).getAction()); - assertEquals(Manifest.permission.READ_PRIVILEGED_PHONE_STATE, - stringArgumentCaptor.getAllValues().get(1)); */ - - SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext); - verify(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord( - eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1)); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - verify(mSubscriptionController, times(1)).setMccMnc(FAKE_MCC_MNC_1, FAKE_SUB_ID_1); - verify(mSubscriptionController, times(0)).clearSubInfo(); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_LOADED)); - - // ACTION_USER_UNLOCKED should trigger another SIM_STATE_CHANGED - Intent intentSimStateChanged = new Intent(Intent.ACTION_USER_UNLOCKED); - mContext.sendBroadcast(intentSimStateChanged); - processAllMessages(); - - // verify SIM_STATE_CHANGED broadcast - /* todo: cannot verify as intent is sent using ActivityManagerNative.broadcastStickyIntent() - * uncomment code below when that is fixed - */ - /* verify(mContext, times(4)).sendBroadcast(intentArgumentCaptor.capture(), - stringArgumentCaptor.capture()); - assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED, - intentArgumentCaptor.getAllValues().get(2).getAction()); - assertEquals(Manifest.permission.READ_PHONE_STATE, - stringArgumentCaptor.getAllValues().get(2)); - assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED, - intentArgumentCaptor.getAllValues().get(3).getAction()); - assertEquals(Manifest.permission.READ_PRIVILEGED_PHONE_STATE, - stringArgumentCaptor.getAllValues().get(3)); */ - } - - @Test - @SmallTest - public void testSimLoadedEmptyOperatorNumeric() throws Exception { - doReturn(FAKE_ICCID_1).when(mIccRecord).getFullIccId(); - // operator numeric is empty - doReturn("").when(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1); - doReturn(FAKE_SUB_ID_1).when(mSubInfo).getSubscriptionId(); - doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1)); - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext); - verify(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord( - eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1)); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - verify(mSubscriptionController, times(0)).setMccMnc(anyString(), anyInt()); - verify(mSubscriptionController, times(0)).clearSubInfo(); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_LOADED)); - } - - @Test - @SmallTest - public void testSimLockedWithOutIccId() throws Exception { - /* mock no IccId Info present and try to query IccId - after IccId query, update subscriptionDB */ - doReturn("98106240020000000000").when(mIccRecord).getFullIccId(); - - doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1)); - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord( - eq("98106240020000000000"), eq(FAKE_SUB_ID_1)); - - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - verify(mSubscriptionController, times(0)).clearSubInfo(); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_LOCKED)); - } - - @Test - @SmallTest - public void testDualSimLoaded() throws Exception { - // Mock there is two sim cards - replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone}); - replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null, - new String[]{null, null}); - replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 2); - replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null, - new int[]{0, 0}); - replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null, - new int[]{0, 0}); - - doReturn(new int[]{FAKE_SUB_ID_1, FAKE_SUB_ID_2}).when(mSubscriptionManager) - .getActiveSubscriptionIdList(); - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_1)); - doReturn(FAKE_SUB_ID_2).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_2)); - doReturn(2).when(mTelephonyManager).getPhoneCount(); - doReturn(2).when(mTelephonyManager).getActiveModemCount(); - when(mActivityManager.updateMccMncConfiguration(anyString(), anyString())).thenReturn( - true); - doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_1)); - doReturn(FAKE_MCC_MNC_2).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_2)); - verify(mSubscriptionController, times(0)).clearSubInfo(); - doReturn(FAKE_ICCID_1).when(mIccRecord).getFullIccId(); - SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext); - verify(mSubscriptionManager, times(0)).addSubscriptionInfoRecord(anyString(), anyInt()); - verify(mSubscriptionController, times(0)).notifySubscriptionInfoChanged(); - verify(mSubscriptionController, times(0)).setMccMnc(anyString(), anyInt()); - - // Mock sending a sim loaded for SIM 1 - doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1)); - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1); - - processAllMessages(); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(anyString(), anyInt()); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - verify(mSubscriptionController, times(1)).setMccMnc(anyString(), anyInt()); - assertFalse(mUpdater.isSubInfoInitialized()); - - // Mock sending a sim loaded for SIM 2 - doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_2)); - doReturn(FAKE_SUB_ID_2).when(mSubInfo).getSubscriptionId(); - doReturn("89012604200000000001").when(mIccRecord).getFullIccId(); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_2); - - processAllMessages(); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq(FAKE_ICCID_1), - eq(FAKE_SUB_ID_1)); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq("89012604200000000001"), - eq(FAKE_SUB_ID_2)); - verify(mSubscriptionController, times(1)).setMccMnc(eq(FAKE_MCC_MNC_1), eq(FAKE_SUB_ID_1)); - verify(mSubscriptionController, times(1)).setMccMnc(eq(FAKE_MCC_MNC_2), eq(FAKE_SUB_ID_2)); - verify(mSubscriptionController, times(2)).notifySubscriptionInfoChanged(); - assertTrue(mUpdater.isSubInfoInitialized()); - } - - @Test - @SmallTest - public void testSimLockWithIccId() throws Exception { - // ICCID will be queried even if it is already available - doReturn("98106240020000000000").when(mIccRecord).getFullIccId(); - - replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null, - new String[]{FAKE_ICCID_1}); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1); - - processAllMessages(); - assertTrue(mUpdater.isSubInfoInitialized()); - SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord( - anyString(), eq(FAKE_SUB_ID_1)); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - verify(mSubscriptionController, times(0)).clearSubInfo(); - CarrierConfigManager mConfigManager = (CarrierConfigManager) - mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); - /* broadcast is done */ - verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1), - eq(IccCardConstants.INTENT_VALUE_ICC_LOCKED)); - } - - @Test - @SmallTest - public void testUpdateEmbeddedSubscriptions_listSuccess() throws Exception { - when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.createForCardId(anyInt())).thenReturn(mEuiccManager); - when(mEuiccManager.getEid()).thenReturn(FAKE_EID); - - EuiccProfileInfo[] euiccProfiles = new EuiccProfileInfo[] { - new EuiccProfileInfo("1", null /* accessRules */, null /* nickname */), - new EuiccProfileInfo("3", null /* accessRules */, null /* nickname */), - }; - when(mEuiccController.blockingGetEuiccProfileInfoList(FAKE_CARD_ID)).thenReturn( - new GetEuiccProfileInfoListResult( - EuiccService.RESULT_OK, euiccProfiles, false /* removable */)); - - List subInfoList = new ArrayList<>(); - // 1: not embedded, but has matching iccid with an embedded subscription. - subInfoList.add(new SubscriptionInfo.Builder() - .setSimSlotIndex(0) - .setIccId("1") - .build()); - // 2: embedded but no longer present. - subInfoList.add(new SubscriptionInfo.Builder() - .setSimSlotIndex(0) - .setIccId("2") - .setEmbedded(true) - .build()); - - when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate( - new String[] { "1", "3"}, false /* removable */)).thenReturn(subInfoList); - - List cardIds = new ArrayList<>(); - cardIds.add(FAKE_CARD_ID); - mUpdater.updateEmbeddedSubscriptions(cardIds, null /* callback */); - processAllMessages(); - - // 3 is new and so a new entry should have been created. - verify(mSubscriptionController).insertEmptySubInfoRecord( - "3", SubscriptionManager.SIM_NOT_INSERTED); - // 1 already existed, so no new entries should be created for it. - verify(mSubscriptionController, times(0)).clearSubInfo(); - verify(mSubscriptionController, never()).insertEmptySubInfoRecord(eq("1"), anyInt()); - - // Info for 1 and 3 should be updated as active embedded subscriptions. - ArgumentCaptor iccid1Values = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), iccid1Values.capture(), - eq(SubscriptionManager.ICC_ID + "='1'"), isNull()); - assertEquals(1, - iccid1Values.getValue().getAsInteger(SubscriptionManager.IS_EMBEDDED).intValue()); - ArgumentCaptor iccid3Values = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), iccid3Values.capture(), - eq(SubscriptionManager.ICC_ID + "='3'"), isNull()); - assertEquals(1, - iccid3Values.getValue().getAsInteger(SubscriptionManager.IS_EMBEDDED).intValue()); - - // 2 should have been removed since it was returned from the cache but was not present - // in the list provided by the LPA. - ArgumentCaptor iccid2Values = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), iccid2Values.capture(), - eq(SubscriptionManager.ICC_ID + " IN ('2')"), isNull()); - assertEquals(0, - iccid2Values.getValue().getAsInteger(SubscriptionManager.IS_EMBEDDED).intValue()); - } - - @Test - @SmallTest - public void testUpdateEmbeddedSubscriptions_listFailure() throws Exception { - when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccController.blockingGetEuiccProfileInfoList(FAKE_CARD_ID)) - .thenReturn(new GetEuiccProfileInfoListResult( - 42, null /* subscriptions */, false /* removable */)); - - List subInfoList = new ArrayList<>(); - // 1: not embedded, but has matching iccid with an embedded subscription. - subInfoList.add(new SubscriptionInfo.Builder() - .setSimSlotIndex(0) - .setIccId("1") - .build()); - // 2: embedded. - subInfoList.add(new SubscriptionInfo.Builder() - .setSimSlotIndex(0) - .setIccId("2") - .setEmbedded(true) - .build()); - - when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate( - new String[0], false /* removable */)).thenReturn(subInfoList); - - ArrayList cardIds = new ArrayList<>(1); - cardIds.add(FAKE_CARD_ID); - mUpdater.updateEmbeddedSubscriptions(cardIds, null /* callback */); - - // No new entries should be created. - verify(mSubscriptionController, times(0)).clearSubInfo(); - verify(mSubscriptionController, never()).insertEmptySubInfoRecord(anyString(), anyInt()); - - // No existing entries should have been updated. - verify(mContentProvider, never()).update(eq(SubscriptionManager.CONTENT_URI), any(), - any(), isNull()); - } - - @Test - @SmallTest - public void testUpdateEmbeddedSubscriptions_emptyToEmpty() throws Exception { - when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccController.blockingGetEuiccProfileInfoList(FAKE_CARD_ID)) - .thenReturn(new GetEuiccProfileInfoListResult( - 42, null /* subscriptions */, true /* removable */)); - - List subInfoList = new ArrayList<>(); - // 1: not embedded. - subInfoList.add(new SubscriptionInfo.Builder() - .setSimSlotIndex(0) - .setIccId("1") - .build()); - - when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate( - new String[0], false /* removable */)).thenReturn(subInfoList); - - ArrayList cardIds = new ArrayList<>(1); - cardIds.add(FAKE_CARD_ID); - mUpdater.updateEmbeddedSubscriptions(cardIds, null /* callback */); - - // No new entries should be created. - verify(mSubscriptionController, never()).insertEmptySubInfoRecord(anyString(), anyInt()); - - // No existing entries should have been updated. - verify(mContentProvider, never()).update(eq(SubscriptionManager.CONTENT_URI), any(), - any(), isNull()); - } - - @Test - @SmallTest - public void testHexIccIdSuffix() throws Exception { - doReturn(null).when(mSubscriptionController) - .getSubInfoUsingSlotIndexPrivileged(anyInt()); - verify(mSubscriptionController, times(0)).clearSubInfo(); - doReturn("890126042000000000Ff").when(mIccRecord).getFullIccId(); - - // Mock sending a sim loaded for SIM 1 - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_LOADED, "TESTING", FAKE_SUB_ID_1); - - processAllMessages(); - - SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq("890126042000000000"), - eq(FAKE_SUB_ID_1)); - verify(mSubscriptionController, times(0)).clearSubInfo(); - } - - PersistableBundle getCarrierConfigForSubInfoUpdate( - boolean isOpportunistic, String groupUuid) { - PersistableBundle p = new PersistableBundle(); - p.putBoolean(CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, isOpportunistic); - p.putString(CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, groupUuid); - return p; - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigOpportunisticUnchanged() throws Exception { - final int phoneId = mPhone.getPhoneId(); - String carrierPackageName = "FakeCarrierPackageName"; - - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId); - doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1)); - doReturn(carrierPackageName).when(mTelephonyManager) - .getCarrierServicePackageNameForLogicalSlot(eq(phoneId)); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - - mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(), - carrierPackageName, new PersistableBundle()); - - //at each call to updateSubscriptionByCarrierConfig, only carrier certs are updated - verify(mContentProvider, times(1)).update(any(), any(), any(), any()); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList(); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigOpportunisticSetOpportunistic() throws Exception { - final int phoneId = mPhone.getPhoneId(); - PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdate( - true, ""); - String carrierPackageName = "FakeCarrierPackageName"; - - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId); - doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1)); - doReturn(false).when(mSubInfo).isOpportunistic(); - doReturn(carrierPackageName).when(mTelephonyManager) - .getCarrierServicePackageNameForLogicalSlot(eq(phoneId)); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - - mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(), - carrierPackageName, carrierConfig); - - ArgumentCaptor cvCaptor = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider, times(1)).update( - eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)), - cvCaptor.capture(), eq(null), eq(null)); - assertEquals(1, cvCaptor.getValue().getAsInteger( - SubscriptionManager.IS_OPPORTUNISTIC).intValue()); - // 2 updates: isOpportunistic, and carrier certs: - assertEquals(2, cvCaptor.getValue().size()); - verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList(); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testOpportunisticSubscriptionNotUnsetWithEmptyConfigKey() throws Exception { - final int phoneId = mPhone.getPhoneId(); - PersistableBundle carrierConfig = new PersistableBundle(); - - String carrierPackageName = "FakeCarrierPackageName"; - - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId); - doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1)); - doReturn(true).when(mSubInfo).isOpportunistic(); - doReturn(carrierPackageName).when(mTelephonyManager) - .getCarrierServicePackageNameForLogicalSlot(eq(phoneId)); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - - mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(), - carrierPackageName, carrierConfig); - - ArgumentCaptor cvCaptor = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider, times(1)).update( - eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)), - cvCaptor.capture(), eq(null), eq(null)); - // no key is added for the opportunistic bit - assertNull(cvCaptor.getValue().getAsInteger(SubscriptionManager.IS_OPPORTUNISTIC)); - // only carrier certs updated - assertEquals(1, cvCaptor.getValue().size()); - verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList(); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigOpportunisticAddToGroup() throws Exception { - final int phoneId = mPhone.getPhoneId(); - PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdate( - true, "11111111-2222-3333-4444-555555555555"); - String carrierPackageName = "FakeCarrierPackageName"; - - doReturn(true).when(mSubscriptionController).canPackageManageGroup( - ParcelUuid.fromString("11111111-2222-3333-4444-555555555555"), carrierPackageName); - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId); - doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1)); - doReturn(carrierPackageName).when(mTelephonyManager) - .getCarrierServicePackageNameForLogicalSlot(eq(phoneId)); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - - mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(), - carrierPackageName, carrierConfig); - - ArgumentCaptor cvCaptor = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider, times(1)).update( - eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)), - cvCaptor.capture(), eq(null), eq(null)); - assertEquals(1, cvCaptor.getValue().getAsInteger( - SubscriptionManager.IS_OPPORTUNISTIC).intValue()); - assertEquals("11111111-2222-3333-4444-555555555555", - cvCaptor.getValue().getAsString(SubscriptionManager.GROUP_UUID)); - assertEquals(carrierPackageName, - cvCaptor.getValue().getAsString(SubscriptionManager.GROUP_OWNER)); - // 4 updates: isOpportunistic, groupUuid, groupOwner, and carrier certs: - assertEquals(4, cvCaptor.getValue().size()); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigOpportunisticRemoveFromGroup() throws Exception { - final int phoneId = mPhone.getPhoneId(); - PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdate( - true, "00000000-0000-0000-0000-000000000000"); - String carrierPackageName = "FakeCarrierPackageName"; - - doReturn(true).when(mSubscriptionController).canPackageManageGroup( - ParcelUuid.fromString("11111111-2222-3333-4444-555555555555"), carrierPackageName); - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId); - doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1)); - doReturn(ParcelUuid.fromString("11111111-2222-3333-4444-555555555555")) - .when(mSubInfo).getGroupUuid(); - doReturn(carrierPackageName).when(mTelephonyManager) - .getCarrierServicePackageNameForLogicalSlot(eq(phoneId)); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - - mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(), - carrierPackageName, carrierConfig); - - ArgumentCaptor cvCaptor = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider, times(1)).update( - eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)), - cvCaptor.capture(), eq(null), eq(null)); - assertEquals(1, cvCaptor.getValue().getAsInteger( - SubscriptionManager.IS_OPPORTUNISTIC).intValue()); - assertNull(cvCaptor.getValue().getAsString(SubscriptionManager.GROUP_UUID)); - // 3 updates: isOpportunistic, groupUuid, and carrier certs: - assertEquals(3, cvCaptor.getValue().size()); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigPreferredUsageSettingDataCentric() throws Exception { - testUpdateFromCarrierConfigPreferredUsageSetting( - SubscriptionManager.USAGE_SETTING_UNKNOWN, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigPreferredUsageSettingDataCentric2() throws Exception { - testUpdateFromCarrierConfigPreferredUsageSetting( - SubscriptionManager.USAGE_SETTING_DEFAULT, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigPreferredUsageSettingDefault() throws Exception { - testUpdateFromCarrierConfigPreferredUsageSetting( - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - SubscriptionManager.USAGE_SETTING_DEFAULT, - SubscriptionManager.USAGE_SETTING_DEFAULT); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigPreferredUsageSettingNoChange() throws Exception { - testUpdateFromCarrierConfigPreferredUsageSetting( - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC); - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigPreferredUsageSettingInvalid() throws Exception { - testUpdateFromCarrierConfigPreferredUsageSetting( - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - SubscriptionManager.USAGE_SETTING_UNKNOWN, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC); - } - - private PersistableBundle getCarrierConfigForSubInfoUpdateUsageSetting( - @SubscriptionManager.UsageSetting int usageSetting) { - PersistableBundle p = new PersistableBundle(); - p.putString(CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, ""); - p.putBoolean(CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, false); - p.putInt(CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT, usageSetting); - return p; - } - - private void testUpdateFromCarrierConfigPreferredUsageSetting( - int initialSetting, int requestedSetting, int expectedSetting) throws Exception { - final String carrierPackageName = "FakeCarrierPackageName"; - final int phoneId = mPhone.getPhoneId(); - - // Install fixtures, ensure the test will hit the right code path - doReturn(carrierPackageName).when(mTelephonyManager) - .getCarrierServicePackageNameForLogicalSlot(eq(phoneId)); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - - // Setup overlay - setupUsageSettingResources(); - - // Setup subscription - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId); - doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1)); - doReturn(null).when(mSubInfo).getGroupUuid(); - doReturn(false).when(mSubInfo).isOpportunistic(); - doReturn(initialSetting).when(mSubInfo).getUsageSetting(); - - // Get a config bundle for that prefers data centric - PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdateUsageSetting( - requestedSetting); - - mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(), - carrierPackageName, carrierConfig); - - ArgumentCaptor cvCaptor = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider, times(1)).update( - eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)), - cvCaptor.capture(), eq(null), eq(null)); - - if (initialSetting != expectedSetting) { - assertEquals(expectedSetting, - (int) cvCaptor.getValue().getAsInteger(SubscriptionManager.USAGE_SETTING)); - } else { - // If the content value was not set, the captor value will be null - assertNull(cvCaptor.getValue().getAsInteger(SubscriptionManager.USAGE_SETTING)); - } - } - - @Test - @SmallTest - public void testUpdateFromCarrierConfigCarrierCertificates() { - String[] certs = new String[2]; - certs[0] = "d1f1"; - certs[1] = "b5d6"; - - UiccAccessRule[] carrierConfigAccessRules = new UiccAccessRule[certs.length]; - for (int i = 0; i < certs.length; i++) { - carrierConfigAccessRules[i] = new UiccAccessRule( - IccUtils.hexStringToBytes(certs[i]), null, 0); - } - - final int phoneId = mPhone.getPhoneId(); - PersistableBundle carrierConfig = new PersistableBundle(); - carrierConfig.putStringArray( - CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, certs); - - String carrierPackageName = "FakeCarrierPackageName"; - - doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId); - doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1)); - doReturn(false).when(mSubInfo).isOpportunistic(); - doReturn(carrierPackageName).when(mTelephonyManager) - .getCarrierServicePackageNameForLogicalSlot(eq(phoneId)); - ((MockContentResolver) mContext.getContentResolver()).addProvider( - SubscriptionManager.CONTENT_URI.getAuthority(), - new FakeSubscriptionContentProvider()); - - mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(), - carrierPackageName, carrierConfig); - - ArgumentCaptor cvCaptor = ArgumentCaptor.forClass(ContentValues.class); - verify(mContentProvider, times(1)).update( - eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)), - cvCaptor.capture(), eq(null), eq(null)); - assertEquals(carrierConfigAccessRules, UiccAccessRule.decodeRules(cvCaptor.getValue() - .getAsByteArray(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS))); - assertEquals(1, cvCaptor.getValue().size()); - verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList(); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testSimReady() throws Exception { - replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,new String[]{""}); - doReturn(mUiccPort).when(mUiccController).getUiccPort(anyInt()); - doReturn(FAKE_ICCID_1).when(mUiccPort).getIccId(); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_READY, "TESTING", FAKE_SUB_ID_1); - processAllMessages(); - - verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_SUB_ID_1)); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord( - eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1)); - assertTrue(mUpdater.isSubInfoInitialized()); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - @Test - @SmallTest - public void testSimReadyAndLoaded() throws Exception { - replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,new String[]{""}); - - doReturn(mUiccPort).when(mUiccController).getUiccPort(anyInt()); - doReturn(null).when(mUiccPort).getIccId(); - - mUpdater.updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_READY, "TESTING", FAKE_SUB_ID_1); - processAllMessages(); - - verify(mSubscriptionManager, times(0)).addSubscriptionInfoRecord( - eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1)); - - loadSim(); - - SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext); - verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord( - eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1)); - verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged(); - } - - private void setupUsageSettingResources() { - // The most common case, request a voice-centric->data-centric change - mContextFixture.putIntResource( - com.android.internal.R.integer.config_default_cellular_usage_setting, - SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC); - mContextFixture.putIntArrayResource( - com.android.internal.R.array.config_supported_cellular_usage_settings, - new int[]{ - SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC}); - } - - @Test - @SmallTest - public void testCalculateUsageSetting() throws Exception { - setupUsageSettingResources(); - assertEquals(SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, - mUpdater.calculateUsageSetting( - SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC)); - - // Test that a voice-centric-only device only allows voice-centric configuration - mContextFixture.putIntArrayResource( - com.android.internal.R.array.config_supported_cellular_usage_settings, - new int[]{SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC}); - - assertEquals(SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC, - mUpdater.calculateUsageSetting( - SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC, - SubscriptionManager.USAGE_SETTING_DATA_CENTRIC)); - } -} diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyAdminReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyAdminReceiverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..118daa5545dd2c8d35e4f935dd71df6be314c124 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyAdminReceiverTest.java @@ -0,0 +1,95 @@ +/* + * 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; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.os.UserManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class TelephonyAdminReceiverTest extends TelephonyTest { + + private TelephonyAdminReceiver mTelephonyAdminReceiver; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + mTelephonyAdminReceiver = new TelephonyAdminReceiver(mContext, mPhone); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test_nullUserManager() { + mUserManager = null; + TelephonyAdminReceiver telephonyAdminReceiver = new TelephonyAdminReceiver(mContext, + mPhone); + assertFalse(telephonyAdminReceiver.isCellular2gDisabled()); + } + + @Test + public void test_nullIntent_noUpdate() { + assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled()); + + mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)); + + verify(mPhone, never()).sendSubscriptionSettings(anyBoolean()); + assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled()); + } + + @Test + public void test_userRestrictionsNotChanged_noUpdate() { + assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled()); + when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(false); + + mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)); + + verify(mPhone, never()).sendSubscriptionSettings(anyBoolean()); + assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled()); + } + + @Test + public void test_userRestrictionToggled_shouldUpdate() { + assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled()); + when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn( + true).thenReturn(false); + + mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)); + assertTrue(mTelephonyAdminReceiver.isCellular2gDisabled()); + + mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)); + assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled()); + verify(mPhone, times(2)).sendSubscriptionSettings(false); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java index 0e6e2f7f42b8ee3e836d70655434bb915c7fa345..a053c56131fb6a537f5bb2a93ea1b614a5c9328c 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -37,6 +38,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.ServiceManager; +import android.os.UserHandle; import android.permission.LegacyPermissionManager; import android.provider.DeviceConfig; import android.provider.Settings; @@ -530,6 +532,14 @@ public class TelephonyPermissionsTest { } } + @Test + public void testCheckSubscriptionAssociatedWithUser_emergencyNumber() { + doReturn(true).when(mTelephonyManagerMock).isEmergencyNumber(anyString()); + + assertTrue(TelephonyPermissions.checkSubscriptionAssociatedWithUser(mMockContext, SUB_ID, + UserHandle.SYSTEM, "911")); + } + // Put mMockTelephony into service cache so that TELEPHONY_SUPPLIER will get it. private void setTelephonyMockAsService() throws Exception { when(mMockTelephonyBinder.queryLocalInterface(anyString())).thenReturn(mMockTelephony); diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java index 91dd89d50da167cca190d48a1a5133607c621ebd..35a31860462c70c7c047a7234a6b3bb88cf132b5 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java @@ -25,7 +25,9 @@ import static android.telephony.TelephonyManager.RADIO_POWER_ON; import static android.telephony.TelephonyManager.RADIO_POWER_UNAVAILABLE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -35,9 +37,11 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.Manifest; import android.content.Intent; import android.content.pm.UserInfo; import android.net.LinkProperties; +import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -108,6 +112,8 @@ public class TelephonyRegistryTest extends TelephonyTest { private CellLocation mCellLocation; private List mCellInfo; private BarringInfo mBarringInfo = null; + private CellIdentity mCellIdentityForRegiFail; + private int mRegistrationFailReason; // All events contribute to TelephonyRegistry#isPhoneStatePermissionRequired private static final Set READ_PHONE_STATE_EVENTS; @@ -151,6 +157,8 @@ public class TelephonyRegistryTest extends TelephonyTest { TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED); READ_PRIVILEGED_PHONE_STATE_EVENTS.add( TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED); + READ_PRIVILEGED_PHONE_STATE_EVENTS.add( + TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED); } // All events contribute to TelephonyRegistry#isActiveEmergencySessionPermissionRequired @@ -176,7 +184,8 @@ public class TelephonyRegistryTest extends TelephonyTest { TelephonyCallback.CellLocationListener, TelephonyCallback.ServiceStateListener, TelephonyCallback.CellInfoListener, - TelephonyCallback.BarringInfoListener { + TelephonyCallback.BarringInfoListener, + TelephonyCallback.RegistrationFailedListener { // This class isn't mockable to get invocation counts because the IBinder is null and // crashes the TelephonyRegistry. Make a cheesy verify(times()) alternative. public AtomicInteger invocationCount = new AtomicInteger(0); @@ -250,6 +259,15 @@ public class TelephonyRegistryTest extends TelephonyTest { invocationCount.incrementAndGet(); mBarringInfo = barringInfo; } + + public void onRegistrationFailed(@android.annotation.NonNull CellIdentity cellIdentity, + @android.annotation.NonNull String chosenPlmn, + @NetworkRegistrationInfo.Domain int domain, + int causeCode, int additionalCauseCode) { + invocationCount.incrementAndGet(); + mCellIdentityForRegiFail = cellIdentity; + mRegistrationFailReason = causeCode; + } } private void addTelephonyRegistryService() { @@ -897,24 +915,48 @@ public class TelephonyRegistryTest extends TelephonyTest { } @Test - public void testBarringInfoChanged() { + public void testBarringInfoChangedWithLocationFinePermission() throws Exception { + checkBarringInfoWithLocationPermission(Manifest.permission.ACCESS_FINE_LOCATION); + } + + @Test + public void testBarringInfoChangedLocationCoarsePermission() throws Exception { + checkBarringInfoWithLocationPermission(Manifest.permission.ACCESS_COARSE_LOCATION); + } + + @Test + public void testBarringInfoChangedWithoutLocationPermission() throws Exception { + checkBarringInfoWithLocationPermission(null); + } + + private void checkBarringInfoWithLocationPermission(String permission) throws Exception { // Return a slotIndex / phoneId of 0 for all sub ids given. doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt()); doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex(); doReturn(true).when(mLocationManager).isLocationEnabledForUser(any(UserHandle.class)); + mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU; + doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt()); + mContextFixture.addCallingOrSelfPermission(""); + mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); + mContextFixture.addCallingOrSelfPermission( + android.Manifest.permission.READ_PRECISE_PHONE_STATE); + if (permission != null) { + mContextFixture.addCallingOrSelfPermission(permission); + } + final int subId = 1; int[] events = {TelephonyCallback.EVENT_BARRING_INFO_CHANGED}; SparseArray bsi = new SparseArray(1); - bsi.set(BarringInfo.BARRING_SERVICE_TYPE_MO_DATA, + bsi.set(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE, new BarringInfo.BarringServiceInfo( BarringInfo.BarringServiceInfo.BARRING_TYPE_CONDITIONAL, false /*isConditionallyBarred*/, 30 /*conditionalBarringFactor*/, 10 /*conditionalBarringTimeSeconds*/)); - BarringInfo info = new BarringInfo(new CellIdentityLte(), bsi); - - // Registering for info causes Barring Info to be sent to caller + BarringInfo info = new BarringInfo( + new CellIdentityLte(777, 333, 12345, 222, 13579), bsi); + // 1. Register listener which requires location access. mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(), mContext.getAttributionTag(), mTelephonyCallback.callback, events, true); processAllMessages(); @@ -925,12 +967,115 @@ public class TelephonyRegistryTest extends TelephonyTest { mTelephonyRegistry.notifyBarringInfoChanged(0, subId, info); processAllMessages(); assertEquals(2, mTelephonyCallback.invocationCount.get()); - assertEquals(mBarringInfo, info); + assertEquals(mBarringInfo + .getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE), + info.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE)); + String log = mBarringInfo.toString(); + assertTrue(log.contains("777")); + assertTrue(log.contains("333")); + if (permission != null && permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { + assertTrue(log.contains("12345")); + assertTrue(log.contains("222")); + assertTrue(log.contains("13579")); + } else { + assertFalse(log.contains("12345")); + assertFalse(log.contains("222")); + assertFalse(log.contains("13579")); + } // Duplicate BarringInfo notifications do not trigger callback mTelephonyRegistry.notifyBarringInfoChanged(0, subId, info); processAllMessages(); assertEquals(2, mTelephonyCallback.invocationCount.get()); + + mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(), + mContext.getAttributionTag(), mTelephonyCallback.callback, new int[0], true); + // 2. Register listener renounces location access. + mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(), + mContext.getAttributionTag(), mTelephonyCallback.callback, events, true); + processAllMessages(); + // check receiving barring info without location info. + assertEquals(3, mTelephonyCallback.invocationCount.get()); + assertNotNull(mBarringInfo); + assertEquals(mBarringInfo + .getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE), + info.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE)); + log = mBarringInfo.toString(); + assertTrue(log.contains("777")); + assertTrue(log.contains("333")); + assertFalse(log.contains("12345")); + assertFalse(log.contains("222")); + assertFalse(log.contains("13579")); + } + + @Test + public void testRegistrationFailedEventWithLocationFinePermission() throws Exception { + checkRegistrationFailedEventWithLocationPermission( + Manifest.permission.ACCESS_FINE_LOCATION); + } + @Test + public void testRegistrationFailedEventWithLocationCoarsePermission() throws Exception { + checkRegistrationFailedEventWithLocationPermission( + Manifest.permission.ACCESS_COARSE_LOCATION); + } + + @Test + public void testRegistrationFailedEventWithoutLocationPermission() throws Exception { + checkRegistrationFailedEventWithLocationPermission(null); + } + + private void checkRegistrationFailedEventWithLocationPermission(String permission) + throws Exception { + // Return a slotIndex / phoneId of 0 for all sub ids given. + doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt()); + doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex(); + doReturn(true).when(mLocationManager).isLocationEnabledForUser(any(UserHandle.class)); + + mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU; + doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt()); + mContextFixture.addCallingOrSelfPermission(""); + mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); + mContextFixture.addCallingOrSelfPermission( + android.Manifest.permission.READ_PRECISE_PHONE_STATE); + if (permission != null) { + mContextFixture.addCallingOrSelfPermission(permission); + } + + final int subId = 1; + int[] events = {TelephonyCallback.EVENT_REGISTRATION_FAILURE}; + CellIdentity cellIdentity = + new CellIdentityLte(777, 333, 12345, 227, 13579); + + // 1. Register listener which requires location access. + mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(), + mContext.getAttributionTag(), mTelephonyCallback.callback, events, true); + processAllMessages(); + int invocationCount = mTelephonyCallback.invocationCount.get(); + // Updating the RegistrationFailed info to be updated + mTelephonyRegistry.notifyRegistrationFailed( + 0, subId, cellIdentity, "88888", 1, 333, 22); + processAllMessages(); + assertEquals(invocationCount + 1, mTelephonyCallback.invocationCount.get()); + if (permission != null && permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { + assertEquals(cellIdentity, mCellIdentityForRegiFail); + } else { + assertEquals(cellIdentity.sanitizeLocationInfo(), mCellIdentityForRegiFail); + } + assertEquals(333, mRegistrationFailReason); + mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(), + mContext.getAttributionTag(), mTelephonyCallback.callback, new int[0], true); + + // 2. Register listener which renounces location access. + mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(), + mContext.getAttributionTag(), mTelephonyCallback.callback, events, true); + invocationCount = mTelephonyCallback.invocationCount.get(); + // Updating the RegistrationFailed info to be updated + mTelephonyRegistry.notifyRegistrationFailed( + 0, subId, cellIdentity, "88888", 1, 555, 22); + processAllMessages(); + assertEquals(invocationCount + 1, mTelephonyCallback.invocationCount.get()); + assertEquals(cellIdentity.sanitizeLocationInfo(), mCellIdentityForRegiFail); + assertEquals(555, mRegistrationFailReason); } /** @@ -1170,12 +1315,9 @@ public class TelephonyRegistryTest extends TelephonyTest { final int subId = 1; // Return a slotIndex / phoneId of 0 for subId 1. - doReturn(subId).when(mSubscriptionController).getSubId(phoneId); + doReturn(subId).when(mSubscriptionManagerService).getSubId(phoneId); doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(subId); doReturn(phoneId).when(mMockSubInfo).getSimSlotIndex(); - mServiceManagerMockedServices.put("isub", mSubscriptionController); - doReturn(mSubscriptionController).when(mSubscriptionController) - .queryLocalInterface(anyString()); UserInfo userInfo = new UserInfo(UserHandle.myUserId(), "" /* name */, 0 /* flags */); doReturn(userInfo.id).when(mIActivityManager).getCurrentUserId(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java index 6bed1b627cab0e3ecb40f565a326b9504487b531..b044814765e1421979e5e8cc92f5a98930661613 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java @@ -16,7 +16,8 @@ package com.android.internal.telephony; -import static org.junit.Assert.assertNotNull; +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN; + import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; @@ -107,13 +108,17 @@ import com.android.internal.telephony.data.LinkBandwidthEstimator; import com.android.internal.telephony.data.PhoneSwitcher; import com.android.internal.telephony.emergency.EmergencyNumberTracker; import com.android.internal.telephony.imsphone.ImsExternalCallTracker; +import com.android.internal.telephony.imsphone.ImsNrSaModeHandler; import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.imsphone.ImsPhoneCallTracker; +import com.android.internal.telephony.metrics.DeviceStateHelper; import com.android.internal.telephony.metrics.ImsStats; import com.android.internal.telephony.metrics.MetricsCollector; import com.android.internal.telephony.metrics.PersistAtomsStorage; +import com.android.internal.telephony.metrics.ServiceStateStats; import com.android.internal.telephony.metrics.SmsStats; import com.android.internal.telephony.metrics.VoiceCallSessionStats; +import com.android.internal.telephony.satellite.SatelliteController; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.test.SimulatedCommands; import com.android.internal.telephony.test.SimulatedCommandsVerifier; @@ -203,7 +208,6 @@ public abstract class TelephonyTest { protected GsmCdmaCall mGsmCdmaCall; protected ImsCall mImsCall; protected ImsEcbm mImsEcbm; - protected SubscriptionController mSubscriptionController; protected SubscriptionManagerService mSubscriptionManagerService; protected ServiceState mServiceState; protected IPackageManager.Stub mMockPackageManager; @@ -232,6 +236,7 @@ public abstract class TelephonyTest { protected CarrierSignalAgent mCarrierSignalAgent; protected CarrierActionAgent mCarrierActionAgent; protected ImsExternalCallTracker mImsExternalCallTracker; + protected ImsNrSaModeHandler mImsNrSaModeHandler; protected AppSmsManager mAppSmsManager; protected IccSmsInterfaceManager mIccSmsInterfaceManager; protected SmsDispatchersController mSmsDispatchersController; @@ -240,7 +245,6 @@ public abstract class TelephonyTest { protected IntentBroadcaster mIntentBroadcaster; protected NitzStateMachine mNitzStateMachine; protected RadioConfig mMockRadioConfig; - protected SubscriptionInfoUpdater mSubInfoRecordUpdater; protected LocaleTracker mLocaleTracker; protected RestrictedState mRestrictedState; protected PhoneConfigurationManager mPhoneConfigurationManager; @@ -266,6 +270,9 @@ public abstract class TelephonyTest { protected CellLocation mCellLocation; protected DataServiceManager mMockedWwanDataServiceManager; protected DataServiceManager mMockedWlanDataServiceManager; + protected ServiceStateStats mServiceStateStats; + protected SatelliteController mSatelliteController; + protected DeviceStateHelper mDeviceStateHelper; // Initialized classes protected ActivityManager mActivityManager; @@ -437,7 +444,6 @@ public abstract class TelephonyTest { mGsmCdmaCall = Mockito.mock(GsmCdmaCall.class); mImsCall = Mockito.mock(ImsCall.class); mImsEcbm = Mockito.mock(ImsEcbm.class); - mSubscriptionController = Mockito.mock(SubscriptionController.class); mSubscriptionManagerService = Mockito.mock(SubscriptionManagerService.class); mServiceState = Mockito.mock(ServiceState.class); mMockPackageManager = Mockito.mock(IPackageManager.Stub.class); @@ -466,6 +472,7 @@ public abstract class TelephonyTest { mCarrierSignalAgent = Mockito.mock(CarrierSignalAgent.class); mCarrierActionAgent = Mockito.mock(CarrierActionAgent.class); mImsExternalCallTracker = Mockito.mock(ImsExternalCallTracker.class); + mImsNrSaModeHandler = Mockito.mock(ImsNrSaModeHandler.class); mAppSmsManager = Mockito.mock(AppSmsManager.class); mIccSmsInterfaceManager = Mockito.mock(IccSmsInterfaceManager.class); mSmsDispatchersController = Mockito.mock(SmsDispatchersController.class); @@ -474,7 +481,6 @@ public abstract class TelephonyTest { mIntentBroadcaster = Mockito.mock(IntentBroadcaster.class); mNitzStateMachine = Mockito.mock(NitzStateMachine.class); mMockRadioConfig = Mockito.mock(RadioConfig.class); - mSubInfoRecordUpdater = Mockito.mock(SubscriptionInfoUpdater.class); mLocaleTracker = Mockito.mock(LocaleTracker.class); mRestrictedState = Mockito.mock(RestrictedState.class); mPhoneConfigurationManager = Mockito.mock(PhoneConfigurationManager.class); @@ -500,6 +506,9 @@ public abstract class TelephonyTest { mCellLocation = Mockito.mock(CellLocation.class); mMockedWwanDataServiceManager = Mockito.mock(DataServiceManager.class); mMockedWlanDataServiceManager = Mockito.mock(DataServiceManager.class); + mServiceStateStats = Mockito.mock(ServiceStateStats.class); + mSatelliteController = Mockito.mock(SatelliteController.class); + mDeviceStateHelper = Mockito.mock(DeviceStateHelper.class); TelephonyManager.disableServiceHandleCaching(); PropertyInvalidatedCache.disableForTestMode(); @@ -525,7 +534,9 @@ public abstract class TelephonyTest { Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); - enableSubscriptionManagerService(true); + mServiceManagerMockedServices.put("isub", mSubscriptionManagerService); + doReturn(mSubscriptionManagerService).when(mSubscriptionManagerService) + .queryLocalInterface(anyString()); mPhone.mCi = mSimulatedCommands; mCT.mCi = mSimulatedCommands; @@ -585,6 +596,8 @@ public abstract class TelephonyTest { anyInt(), nullable(Object.class)); doReturn(mImsExternalCallTracker).when(mTelephonyComponentFactory) .makeImsExternalCallTracker(nullable(ImsPhone.class)); + doReturn(mImsNrSaModeHandler).when(mTelephonyComponentFactory) + .makeImsNrSaModeHandler(nullable(ImsPhone.class)); doReturn(mAppSmsManager).when(mTelephonyComponentFactory) .makeAppSmsManager(nullable(Context.class)); doReturn(mCarrierSignalAgent).when(mTelephonyComponentFactory) @@ -716,7 +729,14 @@ public abstract class TelephonyTest { doReturn(mPhone).when(mInboundSmsHandler).getPhone(); doReturn(mImsCallProfile).when(mImsCall).getCallProfile(); doReturn(mIBinder).when(mIIntentSender).asBinder(); - doReturn(mIIntentSender).when(mIActivityManager).getIntentSenderWithFeature(anyInt(), + doAnswer(invocation -> { + Intent[] intents = invocation.getArgument(6); + if (intents != null && intents.length > 0) { + doReturn(intents[0]).when(mIActivityManager) + .getIntentForIntentSender(mIIntentSender); + } + return mIIntentSender; + }).when(mIActivityManager).getIntentSenderWithFeature(anyInt(), nullable(String.class), nullable(String.class), nullable(IBinder.class), nullable(String.class), anyInt(), nullable(Intent[].class), nullable(String[].class), anyInt(), nullable(Bundle.class), anyInt()); @@ -725,22 +745,20 @@ public abstract class TelephonyTest { doReturn(TelephonyManager.PHONE_TYPE_GSM).when(mTelephonyManager).getPhoneType(); doReturn(mServiceState).when(mSST).getServiceState(); + doReturn(mServiceStateStats).when(mSST).getServiceStateStats(); mSST.mSS = mServiceState; mSST.mRestrictedState = mRestrictedState; mServiceManagerMockedServices.put("connectivity_metrics_logger", mConnMetLoggerBinder); mServiceManagerMockedServices.put("package", mMockPackageManager); mServiceManagerMockedServices.put("legacy_permission", mMockLegacyPermissionManager); logd("mMockLegacyPermissionManager replaced"); - doReturn(new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN, - AccessNetworkConstants.TRANSPORT_TYPE_WLAN}) - .when(mAccessNetworksManager).getAvailableTransports(); doReturn(new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN, AccessNetworkConstants.TRANSPORT_TYPE_WLAN}) .when(mAccessNetworksManager).getAvailableTransports(); doReturn(true).when(mDataSettingsManager).isDataEnabled(); doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo( anyInt(), anyInt()); - doReturn(RIL.RADIO_HAL_VERSION_2_0).when(mPhone).getHalVersion(); + doReturn(RIL.RADIO_HAL_VERSION_2_0).when(mPhone).getHalVersion(anyInt()); doReturn(2).when(mSignalStrength).getLevel(); // WiFi @@ -815,6 +833,11 @@ public abstract class TelephonyTest { doReturn(null).when(mContext).getFileStreamPath(anyString()); doReturn(mPersistAtomsStorage).when(mMetricsCollector).getAtomsStorage(); doReturn(mWifiManager).when(mContext).getSystemService(eq(Context.WIFI_SERVICE)); + doReturn(mDeviceStateHelper).when(mMetricsCollector).getDeviceStateHelper(); + doReturn(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN) + .when(mDeviceStateHelper) + .getFoldState(); + doReturn(null).when(mContext).getSystemService(eq(Context.DEVICE_STATE_SERVICE)); //Use reflection to mock singletons replaceInstance(CallManager.class, "INSTANCE", null, mCallManager); @@ -822,7 +845,6 @@ public abstract class TelephonyTest { mTelephonyComponentFactory); replaceInstance(UiccController.class, "mInstance", null, mUiccController); replaceInstance(CdmaSubscriptionSourceManager.class, "sInstance", null, mCdmaSSM); - replaceInstance(SubscriptionController.class, "sInstance", null, mSubscriptionController); replaceInstance(SubscriptionManagerService.class, "sInstance", null, mSubscriptionManagerService); replaceInstance(ProxyController.class, "sProxyController", null, mProxyController); @@ -843,7 +865,6 @@ public abstract class TelephonyTest { replaceInstance(PhoneFactory.class, "sMadeDefaults", null, true); replaceInstance(PhoneFactory.class, "sPhone", null, mPhone); replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); - replaceInstance(PhoneFactory.class, "sSubInfoRecordUpdater", null, mSubInfoRecordUpdater); replaceInstance(RadioConfig.class, "sRadioConfig", null, mMockRadioConfig); replaceInstance(PhoneConfigurationManager.class, "sInstance", null, mPhoneConfigurationManager); @@ -851,15 +872,10 @@ public abstract class TelephonyTest { mCellularNetworkValidator); replaceInstance(MultiSimSettingController.class, "sInstance", null, mMultiSimSettingController); - replaceInstance(SubscriptionInfoUpdater.class, "sIsSubInfoInitialized", null, true); replaceInstance(PhoneFactory.class, "sCommandsInterfaces", null, new CommandsInterface[] {mSimulatedCommands}); replaceInstance(PhoneFactory.class, "sMetricsCollector", null, mMetricsCollector); - - if (!isSubscriptionManagerServiceEnabled()) { - assertNotNull("Failed to set up SubscriptionController singleton", - SubscriptionController.getInstance()); - } + replaceInstance(SatelliteController.class, "sInstance", null, mSatelliteController); setReady(false); // create default TestableLooper for test and add to list of monitored loopers @@ -958,14 +974,17 @@ public abstract class TelephonyTest { private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED = DeviceConfig.NAMESPACE_PRIVACY + "/" + "device_identifier_access_restrictions_disabled"; + private HashMap mFlags = new HashMap<>(); @Override public Bundle call(String method, String arg, Bundle extras) { + logd("FakeSettingsConfigProvider: call called, method: " + method + + " request: " + arg + ", args=" + extras); + Bundle bundle = new Bundle(); switch (method) { case Settings.CALL_METHOD_GET_CONFIG: { switch (arg) { case PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED: { - Bundle bundle = new Bundle(); bundle.putString( PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED, "0"); @@ -977,6 +996,18 @@ public abstract class TelephonyTest { } break; } + case Settings.CALL_METHOD_LIST_CONFIG: + logd("LIST_config: " + mFlags); + Bundle result = new Bundle(); + result.putSerializable(Settings.NameValueTable.VALUE, mFlags); + return result; + case Settings.CALL_METHOD_SET_ALL_CONFIG: + mFlags = (extras != null) + ? (HashMap) extras.getSerializable(Settings.CALL_METHOD_FLAGS_KEY) + : new HashMap<>(); + bundle.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN, + Settings.SET_ALL_RESULT_SUCCESS); + return bundle; default: fail("Method not expected: " + method); } @@ -1261,22 +1292,4 @@ public abstract class TelephonyTest { } } } - - protected void enableSubscriptionManagerService(boolean enabled) throws Exception { - if (enabled) { - mServiceManagerMockedServices.put("isub", mSubscriptionManagerService); - doReturn(mSubscriptionManagerService).when(mIBinder) - .queryLocalInterface(anyString()); - } - replaceInstance(PhoneFactory.class, "sSubscriptionManagerServiceEnabled", null, enabled); - mContextFixture.putBooleanResource(com.android.internal.R.bool - .config_using_subscription_manager_service, enabled); - doReturn(enabled).when(mPhone).isSubscriptionManagerServiceEnabled(); - doReturn(enabled).when(mPhone2).isSubscriptionManagerServiceEnabled(); - doReturn(enabled).when(mImsPhone).isSubscriptionManagerServiceEnabled(); - } - - protected boolean isSubscriptionManagerServiceEnabled() { - return mPhone.isSubscriptionManagerServiceEnabled(); - } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java index f09c94e1917ca6e80ccb90fd6a77b2e45377dbfd..9f763371f7bb169ce7786dc0f7e6680790a157f3 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java @@ -96,7 +96,7 @@ public class VisualVoicemailSmsFilterTest extends TestCase { VisualVoicemailSmsFilter.setPhoneAccountHandleConverterForTest( new PhoneAccountHandleConverter() { @Override - public PhoneAccountHandle fromSubId(int subId) { + public PhoneAccountHandle fromSubId(int subId, Context context) { return new PhoneAccountHandle( new ComponentName("com.android.internal.telephony", "VisualVoicemailSmsFilterTest"), "foo"); diff --git a/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f2c18708edf8cceb9abe3bc8794e460d547ecf82 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.cat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.os.UserHandle; +import android.telephony.SmsManager; +import android.telephony.SmsMessage; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.ProxyController; +import com.android.internal.telephony.SmsController; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.test.SimulatedCommands; +import com.android.internal.telephony.uicc.IccCardApplicationStatus; +import com.android.internal.telephony.uicc.IccCardStatus; +import com.android.internal.telephony.uicc.IccFileHandler; +import com.android.internal.telephony.uicc.IccIoResult; +import com.android.internal.telephony.uicc.IccUtils; +import com.android.internal.telephony.uicc.UiccCard; +import com.android.internal.telephony.uicc.UiccProfile; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class CATServiceTest extends TelephonyTest { + + private static final String SMS_SENT_ACTION = + "com.android.internal.telephony.cat.SMS_SENT_ACTION"; + private static final String SMS_DELIVERY_ACTION = + "com.android.internal.telephony.cat.SMS_DELIVERY_ACTION"; + //Mocked Classes + @Mock + private RilMessageDecoder mRilMessageDecoder; + private IccFileHandler mIccFileHandler; + private SmsController mSmsController; + private CommandDetails mCommandDetails; + private CatService mCatService; + private IccCardStatus mIccCardStatus; + private IccIoResult mIccIoResult; + private String mData = + "D059810301130082028183051353656E64696E672072657175657374202E2E2E0607911989548056780B" + + "3051FF05812143F500F6082502700000201115001500BFFF01BA23C2169EA9B02D7A7FBAA0" + + "DAABFEE8B8DE9DA06DCD234E"; + private byte[] mRawdata = IccUtils.hexStringToBytes(mData); + private List mCtlvs; + + /** + * Terminal Response with result code in last 3 bytes = length + SMS_RP_ERROR(0x35) + * + ErrorCode(= 41) + */ + private String mTerminalResponseForSmsRpError = "81030113000202828183023529"; + + /** + * Terminal Response with result code in last 3 bytes = length + NETWORK_UNABLE_TO_PROCESS(0x21) + * + ErrorCode(= 41 with 8th bit set to 1) + */ + private String mTerminalResponseForNetworkUnableToProcess = "810301130002028281830221A9"; + + /** + * Terminal Response with result code in last 2 bytes = length + * + TERMINAL_UNABLE_TO_PROCESS(0x20) + */ + private String mTerminalResponseForTerminalUnableToProcess = "810301130002028281830120"; + + //Terminal Response with result code(0x00)for delivery success in last 2 bytes + private String mTerminalResponseForDeliverySuccess = "810301130002028281830100"; + + public CATServiceTest() { + super(); + } + + private IccCardApplicationStatus composeUiccApplicationStatus( + IccCardApplicationStatus.AppType appType, + IccCardApplicationStatus.AppState appState, String aid) { + IccCardApplicationStatus mIccCardAppStatus = new IccCardApplicationStatus(); + mIccCardAppStatus.aid = aid; + mIccCardAppStatus.app_type = appType; + mIccCardAppStatus.aid = aid; + mIccCardAppStatus.app_type = appType; + mIccCardAppStatus.app_state = appState; + mIccCardAppStatus.pin1 = mIccCardAppStatus.pin2 = + IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED; + return mIccCardAppStatus; + } + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + mRilMessageDecoder = mock(RilMessageDecoder.class); + mIccFileHandler = mock(IccFileHandler.class); + mSmsController = mock(SmsController.class); + mIccCardStatus = mock(IccCardStatus.class); + mProxyController = mock(ProxyController.class); + mUiccCard = mock(UiccCard.class); + IccCardApplicationStatus umtsApp = composeUiccApplicationStatus( + IccCardApplicationStatus.AppType.APPTYPE_USIM, + IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA2"); + mIccCardStatus.mApplications = new IccCardApplicationStatus[]{umtsApp}; + mIccCardStatus.mCdmaSubscriptionAppIndex = + mIccCardStatus.mImsSubscriptionAppIndex = + mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1; + mIccIoResult = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes("FF40")); + mSimulatedCommands = mock(SimulatedCommands.class); + mSimulatedCommands.setIccIoResultForApduLogicalChannel(mIccIoResult); + mUiccProfile = new UiccProfile(mContext, mSimulatedCommands, mIccCardStatus, + 0 /* phoneId */, mUiccCard, new Object()); + processAllMessages(); + logd("Created UiccProfile"); + processAllMessages(); + mCatService = CatService.getInstance(mSimulatedCommands, mContext, + mUiccProfile, mUiccController.getSlotIdFromPhoneId(0)); + logd("Created CATService"); + createCommandDetails(); + createComprehensionTlvList(); + } + + @After + public void tearDown() throws Exception { + mCatService.dispose(); + mUiccProfile = null; + mCatService = null; + mCtlvs = null; + mProxyController = null; + mRilMessageDecoder = null; + mCommandDetails = null; + mContext = null; + mSimulatedCommands = null; + mIccCardStatus = null; + mIccCard = null; + mIccFileHandler = null; + mIccIoResult = null; + mSmsController = null; + super.tearDown(); + } + + private void createCommandDetails() { + mCommandDetails = mock(CommandDetails.class); + mCommandDetails.compRequired = true; + mCommandDetails.commandNumber = 1; + mCommandDetails.typeOfCommand = 19; + mCommandDetails.commandQualifier = 0; + } + + private void createComprehensionTlvList() { + ComprehensionTlv ctlv1 = new ComprehensionTlv(1, false, 3, mRawdata, 4); + ComprehensionTlv ctlv2 = new ComprehensionTlv(2, false, 2, mRawdata, 9); + ComprehensionTlv ctlv3 = new ComprehensionTlv(5, false, 19, mRawdata, 13); + ComprehensionTlv ctlv4 = new ComprehensionTlv(6, false, 7, mRawdata, 34); + ComprehensionTlv ctlv5 = new ComprehensionTlv(11, false, 48, mRawdata, 43); + mCtlvs = new ArrayList<>(); + mCtlvs.add(ctlv1); + mCtlvs.add(ctlv2); + mCtlvs.add(ctlv3); + mCtlvs.add(ctlv4); + mCtlvs.add(ctlv5); + } + + @Test + public void testSendSmsCommandParams() throws Exception { + ComprehensionTlv ctlv = new ComprehensionTlv(11, false, 48, mRawdata, 43); + SmsMessage smsMessage = ValueParser.retrieveTpduAsSmsMessage(ctlv); + assertNotNull(smsMessage); + assertEquals("12345", smsMessage.getRecipientAddress()); + } + + @Test + public void testSendSTKSmsViaCatService() { + CommandParams cmdPrms = new CommandParams(mCommandDetails); + when(mProxyController.getSmsController()).thenReturn(mSmsController); + mCatService.sendStkSms("test", "12345", 1, cmdPrms, mProxyController); + verify(mSmsController, Mockito.times(1)).sendTextForSubscriber(anyInt(), + anyString(), nullable(String.class), anyString(), nullable(String.class), + anyString(), Mockito.anyObject(), any(), eq(false), anyLong(), eq(true), eq(true)); + } + + @Test + public void testprocessSMSEventNotify() throws Exception { + CommandParamsFactory cmdPF = CommandParamsFactory.getInstance(mRilMessageDecoder, + mIccFileHandler, mContext); + assertEquals(false, cmdPF.processSMSEventNotify(mCommandDetails, mCtlvs)); + } + + @Test + public void testSkipFdnCheckforSTKSmsViaCatService() { + CommandParams cmdPrms = new CommandParams(mCommandDetails); + when(mProxyController.getSmsController()).thenReturn(mSmsController); + mCatService.sendStkSms("test", "12345", 1, cmdPrms, mProxyController); + verify(mSmsController, Mockito.times(0)).isNumberBlockedByFDN(1, "12345", + "com.android.internal.telephony"); + } + + //Create and assign a PendingResult object in BroadcastReceiver with which resultCode is updated + private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver, int resultCode) { + BroadcastReceiver.PendingResult pendingResult = + new BroadcastReceiver.PendingResult(resultCode, + "resultData", + /* resultExtras= */ null, + BroadcastReceiver.PendingResult.TYPE_UNREGISTERED, + /* ordered= */ true, + /* sticky= */ false, + /* token= */ null, + UserHandle.myUserId(), + /* flags= */ 0); + receiver.setPendingResult(pendingResult); + } + + @Test + public void testSendTerminalResponseForSendSuccess() { + setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver, Activity.RESULT_OK); + Intent intent = new Intent(SMS_SENT_ACTION).putExtra("cmdDetails", mCommandDetails); + intent.putExtra("ims", true); + mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null, + Activity.RESULT_OK, null, null); + processAllMessages(); + verify(mSimulatedCommands, never()).sendTerminalResponse( + any(), any()); + } + + @Test + public void testSendTerminalResponseForSendSmsRpError() { + setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver, + SmsManager.RESULT_ERROR_GENERIC_FAILURE); + Intent intent = new Intent(SMS_SENT_ACTION).putExtra("cmdDetails", mCommandDetails); + intent.putExtra("ims", true); + intent.putExtra("errorCode", 41); + mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null, + SmsManager.RESULT_ERROR_GENERIC_FAILURE, null, null); + processAllMessages(); + //Verify if the command is encoded with correct Result byte as per TS 101.267 + verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse( + eq(mTerminalResponseForSmsRpError), any()); + } + + @Test + public void testSendTerminalResponseForSendSmsNetworkError() { + setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver, + SmsManager.RESULT_ERROR_GENERIC_FAILURE); + Intent intent = new Intent(SMS_SENT_ACTION).putExtra("cmdDetails", mCommandDetails); + intent.putExtra("ims", false); + intent.putExtra("errorCode", 41); + mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null, + SmsManager.RESULT_ERROR_GENERIC_FAILURE, null, null); + processAllMessages(); + //Verify if the command is encoded with correct Result byte as per TS 101.267 + verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse( + eq(mTerminalResponseForNetworkUnableToProcess), any()); + } + + @Test + public void testSendTerminalResponseForDeliveryFailure() { + setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver, + SmsManager.RESULT_ERROR_GENERIC_FAILURE); + Intent intent = new Intent(SMS_DELIVERY_ACTION).putExtra("cmdDetails", mCommandDetails); + mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null, + SmsManager.RESULT_ERROR_GENERIC_FAILURE, null, null); + processAllMessages(); + //Verify if the command is encoded with correct Result byte as per TS 101.267 + verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse( + eq(mTerminalResponseForTerminalUnableToProcess), any()); + } + + @Test + public void testSendTerminalResponseForDeliverySuccess() { + setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver, + Activity.RESULT_OK); + Intent intent = new Intent(SMS_DELIVERY_ACTION).putExtra("cmdDetails", mCommandDetails); + mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null, + Activity.RESULT_OK, null, null); + processAllMessages(); + //Verify if the command is encoded with correct Result byte as per TS 101.267 + verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse( + eq(mTerminalResponseForDeliverySuccess), any()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java index 5df94e5db833d75c32270e0b52de36a96949951f..34459399bbf778aba45a9f96e503549cabe454bb 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java @@ -57,11 +57,6 @@ import com.android.internal.telephony.cdma.sms.SmsEnvelope; import com.android.internal.util.IState; import com.android.internal.util.StateMachine; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -69,6 +64,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class CdmaInboundSmsHandlerTest extends TelephonyTest { @@ -157,7 +157,7 @@ public class CdmaInboundSmsHandlerTest extends TelephonyTest { Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider); mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(mContext, - mSmsStorageMonitor, mPhone, null); + mSmsStorageMonitor, mPhone, null, mTestableLooper.getLooper()); monitorTestableLooper(new TestableLooper(mCdmaInboundSmsHandler.getHandler().getLooper())); processAllMessages(); } diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java index b36a8d40b77a04c2ed20b41f7ed7019645f8056d..4d116a8553c754e6d766c0e2528a62685087255f 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java @@ -18,7 +18,6 @@ package com.android.internal.telephony.data; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -33,8 +32,10 @@ import android.content.ComponentName; import android.content.IntentFilter; import android.content.pm.ServiceInfo; import android.net.NetworkCapabilities; +import android.os.AsyncResult; import android.os.IBinder; import android.os.Looper; +import android.os.Message; import android.os.PersistableBundle; import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants.AccessNetworkType; @@ -120,7 +121,7 @@ public class AccessNetworksManagerTest extends TelephonyTest { processAllMessages(); replaceInstance(AccessNetworksManager.class, "mDataConfigManager", mAccessNetworksManager, mMockedDataConfigManager); - assumeFalse(mAccessNetworksManager.isInLegacyMode()); + logd("-setUp"); } @@ -157,6 +158,32 @@ public class AccessNetworksManagerTest extends TelephonyTest { assertThat(mAccessNetworksManager.isAnyApnOnIwlan()).isTrue(); } + @Test + public void testGuideTransportTypeForEmergencyDataNetwork() throws Exception { + doAnswer(invocation -> { + int accessNetwork = AccessNetworkType.UNKNOWN; + if (invocation.getArguments()[1].equals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)) { + accessNetwork = AccessNetworkType.IWLAN; + } else if (invocation.getArguments()[1] + .equals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) { + accessNetwork = AccessNetworkType.EUTRAN; + } + mQnsCallback.onQualifiedNetworkTypesChanged(ApnSetting.TYPE_EMERGENCY, + new int[]{accessNetwork}); + return null; + }).when(mMockedQns).reportEmergencyDataNetworkPreferredTransportChanged(anyInt(), anyInt()); + + AsyncResult asyncResult = + new AsyncResult(null, AccessNetworkConstants.TRANSPORT_TYPE_WLAN, null); + Message msg = this.mAccessNetworksManager + .obtainMessage(1 /* EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY */, asyncResult); + mAccessNetworksManager.sendMessage(msg); + processAllMessages(); + + assertThat(mAccessNetworksManager.getPreferredTransport(ApnSetting.TYPE_EMERGENCY)) + .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + } + @Test public void testEmptyNetworkTypes() throws Exception { testQualifiedNetworkTypesChanged(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java index 76a3d0a12bd8f06d35f41ea96287cade9c6cf00a..428699fd934f18c60cadfe23241cb76ea16ae248 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java @@ -77,7 +77,6 @@ public class CellularNetworkValidatorTest extends TelephonyTest { doReturn(CAPABILITY_WITH_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager) .getCurrentPhoneCapability(); mValidatorUT = new CellularNetworkValidator(mContext); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); processAllMessages(); 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 d8b93f5eb0fc627d0d1f6fe8b4cec5f7fb114c77..808a15d1d9d2caaaaf54ea09dc03ac6b8485ab23 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java @@ -16,6 +16,8 @@ package com.android.internal.telephony.data; +import static android.telephony.TelephonyManager.HAL_SERVICE_DATA; + import static com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback; import static com.android.internal.telephony.data.DataNetworkController.NetworkRequestList; @@ -142,6 +144,12 @@ public class DataNetworkControllerTest extends TelephonyTest { private static final String FAKE_MMTEL_PACKAGE = "fake.mmtel.package"; private static final String FAKE_RCS_PACKAGE = "fake.rcs.package"; + // Events + private static final int EVENT_SIM_STATE_CHANGED = 9; + private static final int EVENT_REEVALUATE_EXISTING_DATA_NETWORKS = 16; + private static final int EVENT_VOICE_CALL_ENDED = 18; + private static final int EVENT_SUBSCRIPTION_OVERRIDE = 23; + // Mocked classes private PhoneSwitcher mMockedPhoneSwitcher; protected ISub mMockedIsub; @@ -228,6 +236,36 @@ public class DataNetworkControllerTest extends TelephonyTest { .setPreferred(false) .build(); + // Created to test preferred data profiles that apply to different network types + private final DataProfile mGeneralPurposeDataProfileAlternative = new DataProfile.Builder() + .setApnSetting(new ApnSetting.Builder() + .setId(2161) + .setOperatorNumeric("12345") + .setEntryName("internet_supl_mms_apn") + .setApnName("internet_supl_mms_apn") + .setUser("user") + .setPassword("passwd") + .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL + | ApnSetting.TYPE_MMS) + .setProtocol(ApnSetting.PROTOCOL_IPV6) + .setRoamingProtocol(ApnSetting.PROTOCOL_IP) + .setCarrierEnabled(true) + .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE + | TelephonyManager.NETWORK_TYPE_BITMASK_NR + | TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN + | TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT)) + .setLingeringNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE + | TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT + | TelephonyManager.NETWORK_TYPE_BITMASK_UMTS + | TelephonyManager.NETWORK_TYPE_BITMASK_NR)) + .setProfileId(4321) + .setMaxConns(321) + .setWaitTime(456) + .setMaxConnsTime(789) + .build()) + .setPreferred(false) + .build(); + private final DataProfile mImsCellularDataProfile = new DataProfile.Builder() .setApnSetting(new ApnSetting.Builder() .setId(2164) @@ -319,7 +357,7 @@ public class DataNetworkControllerTest extends TelephonyTest { .setApnName("dun_apn") .setUser("user") .setPassword("passwd") - .setApnTypeBitmask(ApnSetting.TYPE_DUN) + .setApnTypeBitmask(ApnSetting.TYPE_DUN | ApnSetting.TYPE_DEFAULT) .setProtocol(ApnSetting.PROTOCOL_IPV6) .setRoamingProtocol(ApnSetting.PROTOCOL_IP) .setCarrierEnabled(true) @@ -516,9 +554,13 @@ public class DataNetworkControllerTest extends TelephonyTest { private void serviceStateChanged(@NetworkType int networkType, @RegistrationState int regState) { - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); serviceStateChanged(networkType, regState, regState, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); @@ -548,9 +590,13 @@ public class DataNetworkControllerTest extends TelephonyTest { @RegistrationState int dataRegState, @RegistrationState int voiceRegState, @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri) { if (dsri == null) { - dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); } ServiceState ss = new ServiceState(); @@ -725,20 +771,12 @@ public class DataNetworkControllerTest extends TelephonyTest { doReturn(true).when(mSST).getPowerStateFromCarrier(); doReturn(true).when(mSST).isConcurrentVoiceAndDataAllowed(); doReturn(PhoneConstants.State.IDLE).when(mCT).getState(); - doReturn("").when(mSubscriptionController).getEnabledMobileDataPolicies(anyInt()); - doReturn(true).when(mSubscriptionController).setEnabledMobileDataPolicies( - anyInt(), anyString()); doReturn(new SubscriptionInfoInternal.Builder().setId(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); List infoList = new ArrayList<>(); infoList.add(mMockSubInfo); - doReturn(infoList).when(mSubscriptionController).getSubscriptionsInGroup( - any(), any(), any()); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); - doReturn(0).when(mSubscriptionController).getPhoneId(1); doReturn(0).when(mSubscriptionManagerService).getPhoneId(1); - doReturn(1).when(mSubscriptionController).getPhoneId(2); doReturn(1).when(mSubscriptionManagerService).getPhoneId(2); for (int transport : new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN, @@ -830,7 +868,8 @@ public class DataNetworkControllerTest extends TelephonyTest { linkBandwidthEstimatorCallbackCaptor.capture()); mLinkBandwidthEstimatorCallback = linkBandwidthEstimatorCallbackCaptor.getValue(); - List profiles = List.of(mGeneralPurposeDataProfile, mImsCellularDataProfile, + List profiles = List.of(mGeneralPurposeDataProfile, + mGeneralPurposeDataProfileAlternative, mImsCellularDataProfile, mImsIwlanDataProfile, mEmergencyDataProfile, mFotaDataProfile, mTetheringDataProfile); @@ -883,7 +922,6 @@ public class DataNetworkControllerTest extends TelephonyTest { doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mAccessNetworksManager) .getPreferredTransportByNetworkCapability(anyInt()); - doReturn(true).when(mDataProfileManager).isDataProfilePreferred(any(DataProfile.class)); doAnswer(invocation -> { ((Runnable) invocation.getArguments()[0]).run(); @@ -897,7 +935,7 @@ public class DataNetworkControllerTest extends TelephonyTest { mDataNetworkControllerUT.registerDataNetworkControllerCallback( mMockedDataNetworkControllerCallback); - mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED, 10/*SIM_STATE_LOADED*/, 0).sendToTarget(); mDataNetworkControllerUT.obtainMessage(8/*EVENT_DATA_SERVICE_BINDING_CHANGED*/, new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, true, null)) @@ -1257,12 +1295,14 @@ public class DataNetworkControllerTest extends TelephonyTest { public void testSimRemovalDataTearDown() throws Exception { testSetupDataNetwork(); - mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED, TelephonyManager.SIM_STATE_ABSENT, 0).sendToTarget(); processAllMessages(); verifyAllDataDisconnected(); verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false)); verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected(); + verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged( + eq(DataCallResponse.LINK_STATUS_INACTIVE)); } @Test @@ -1271,7 +1311,7 @@ public class DataNetworkControllerTest extends TelephonyTest { Mockito.clearInvocations(mMockedDataNetworkControllerCallback); // Insert the SIM again. - mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED, TelephonyManager.SIM_STATE_LOADED, 0).sendToTarget(); processAllMessages(); @@ -1425,7 +1465,8 @@ public class DataNetworkControllerTest extends TelephonyTest { verifyAllDataDisconnected(); verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false)); verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected(); - + verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged( + eq(DataCallResponse.LINK_STATUS_INACTIVE)); Mockito.clearInvocations(mMockedDataNetworkControllerCallback); // Now RAT changes from GSM to UMTS @@ -1480,7 +1521,7 @@ public class DataNetworkControllerTest extends TelephonyTest { // Call ended. doReturn(PhoneConstants.State.IDLE).when(mCT).getState(); - mDataNetworkControllerUT.obtainMessage(18/*EVENT_VOICE_CALL_ENDED*/).sendToTarget(); + mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget(); processAllMessages(); // It should have no internet setup at the beginning. @@ -1585,9 +1626,7 @@ public class DataNetworkControllerTest extends TelephonyTest { boolean isDataEnabled = mDataNetworkControllerUT.getDataSettingsManager().isDataEnabled(); doReturn(mDataNetworkControllerUT.getDataSettingsManager()) .when(mPhone).getDataSettingsManager(); - MultiSimSettingController instance = MultiSimSettingController.getInstance(); - MultiSimSettingController controller = Mockito.spy( - new MultiSimSettingController(mContext, mSubscriptionController)); + MultiSimSettingController controller = Mockito.spy(new MultiSimSettingController(mContext)); doReturn(true).when(controller).isCarrierConfigLoadedForAllSub(); replaceInstance(MultiSimSettingController.class, "sInstance", null, controller); @@ -1637,6 +1676,14 @@ public class DataNetworkControllerTest extends TelephonyTest { verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL); + mDataNetworkControllerUT.obtainMessage(16 /*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/, + DataEvaluation.DataEvaluationReason.DATA_SERVICE_STATE_CHANGED).sendToTarget(); + + processAllFutureMessages(); + + // Make sure IMS network is not torn down + verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_MMS); + // Remove MMS data enabled override mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager .MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED, false); @@ -1673,7 +1720,6 @@ public class DataNetworkControllerTest extends TelephonyTest { @Test public void testIsDataEnabledOverriddenForApnDataDuringCall() throws Exception { doReturn(1).when(mPhone).getSubId(); - doReturn(2).when(mSubscriptionController).getDefaultDataSubId(); doReturn(2).when(mSubscriptionManagerService).getDefaultDataSubId(); // Data disabled mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled( @@ -1716,7 +1762,6 @@ public class DataNetworkControllerTest extends TelephonyTest { // Assume phone2 is the default data phone Phone phone2 = Mockito.mock(Phone.class); replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, phone2}); - doReturn(2).when(mSubscriptionController).getDefaultDataSubId(); doReturn(2).when(mSubscriptionManagerService).getDefaultDataSubId(); // Data disabled on nonDDS @@ -1746,9 +1791,20 @@ public class DataNetworkControllerTest extends TelephonyTest { // Verify internet connection verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET); - // Disable auto data switch mobile policy + // Disable auto data switch mobile policy, but enabled data during call mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager .MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, false); + mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager + .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL, true); + doReturn(PhoneConstants.State.RINGING).when(phone2).getState(); + processAllMessages(); + + // Verify internet connection + verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET); + + // Disable data during call + mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager + .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL, false); processAllMessages(); // Verify no internet connection @@ -1939,7 +1995,7 @@ public class DataNetworkControllerTest extends TelephonyTest { // Set 5G unmetered congestedNetworkTypes.add(TelephonyManager.NETWORK_TYPE_NR); - mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE, NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED, NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED, new int[]{TelephonyManager.NETWORK_TYPE_NR}).sendToTarget(); @@ -1961,7 +2017,7 @@ public class DataNetworkControllerTest extends TelephonyTest { // Set all network types metered congestedNetworkTypes.clear(); - mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE, NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED, 0, TelephonyManager.getAllNetworkTypes()).sendToTarget(); dataNetwork.sendMessage(16/*EVENT_SUBSCRIPTION_PLAN_OVERRIDE*/); @@ -1981,7 +2037,7 @@ public class DataNetworkControllerTest extends TelephonyTest { // Set 5G unmetered unmeteredNetworkTypes.add(TelephonyManager.NETWORK_TYPE_NR); - mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE, NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED, NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED, new int[]{TelephonyManager.NETWORK_TYPE_NR}).sendToTarget(); @@ -2005,7 +2061,7 @@ public class DataNetworkControllerTest extends TelephonyTest { // Set all network types metered unmeteredNetworkTypes.clear(); - mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE, NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED, 0, TelephonyManager.getAllNetworkTypes()).sendToTarget(); dataNetwork.sendMessage(16/*EVENT_SUBSCRIPTION_PLAN_OVERRIDE*/); @@ -2100,7 +2156,7 @@ public class DataNetworkControllerTest extends TelephonyTest { NetworkCapabilities.NET_CAPABILITY_MMTEL); // Both internet and IMS should be retained after network re-evaluation - mDataNetworkControllerUT.obtainMessage(16/*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/) + mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS) .sendToTarget(); processAllMessages(); @@ -2120,7 +2176,7 @@ public class DataNetworkControllerTest extends TelephonyTest { NetworkCapabilities.NET_CAPABILITY_MMTEL); // Both internet and IMS should be retained after network re-evaluation - mDataNetworkControllerUT.obtainMessage(16/*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/) + mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS) .sendToTarget(); processAllMessages(); @@ -2284,6 +2340,8 @@ public class DataNetworkControllerTest extends TelephonyTest { // Verify all data disconnected. verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false)); + verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged( + eq(DataCallResponse.LINK_STATUS_INACTIVE)); // A new data network should be connected on IWLAN List dataNetworkList = getDataNetworks(); @@ -2344,6 +2402,8 @@ public class DataNetworkControllerTest extends TelephonyTest { // Verify all data disconnected. verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false)); + verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged( + eq(DataCallResponse.LINK_STATUS_INACTIVE)); // Should setup a new one instead of handover. verify(mMockedWwanDataServiceManager).setupDataCall(anyInt(), any(DataProfile.class), @@ -2455,6 +2515,7 @@ public class DataNetworkControllerTest extends TelephonyTest { public void testHandoverDataNetworkRetryReachedMaximum() throws Exception { testSetupImsDataNetwork(); + // 1. Normal case setFailedSetupDataResponse(mMockedWlanDataServiceManager, DataFailCause.HANDOVER_FAILED, -1, true); updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS, @@ -2475,6 +2536,30 @@ public class DataNetworkControllerTest extends TelephonyTest { verify(mMockedWlanDataServiceManager).setupDataCall(anyInt(), any(DataProfile.class), anyBoolean(), anyBoolean(), eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), any(), anyBoolean(), any(Message.class)); + + // 2. Active VoPS call, should delay tear down + doReturn(PhoneConstants.State.RINGING).when(mCT).getState(); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, + true); + carrierConfigChanged(); + + setFailedSetupDataResponse(mMockedWwanDataServiceManager, + DataFailCause.HANDOVER_FAILED, -1, true); + updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + processAllFutureMessages(); + + // Verify the network wasn't torn down + verify(mMockedWlanDataServiceManager, never()).deactivateDataCall(anyInt(), + eq(DataService.REQUEST_REASON_NORMAL), any(Message.class)); + + // Verify tear down after call ends + doReturn(PhoneConstants.State.IDLE).when(mCT).getState(); + mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget(); + processAllFutureMessages(); + + verify(mMockedWlanDataServiceManager).deactivateDataCall(anyInt(), + eq(DataService.REQUEST_REASON_NORMAL), any(Message.class)); } @Test @@ -2625,8 +2710,9 @@ public class DataNetworkControllerTest extends TelephonyTest { createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET)); processAllFutureMessages(); - // Should retried 20 times, which is the maximum based on the retry config rules. - verify(mMockedWwanDataServiceManager, times(21)).setupDataCall(anyInt(), + // The first 8 retries are short timers that scheduled by handler, future retries are + // scheduled by intent and require more complex mock, so we only verify the first 8 here. + verify(mMockedWwanDataServiceManager, times(9)).setupDataCall(anyInt(), any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(), any(Message.class)); } @@ -2776,7 +2862,7 @@ public class DataNetworkControllerTest extends TelephonyTest { processAllFutureMessages(); // TAC changes should clear the already-scheduled retry and throttling. - assertThat(mDataNetworkControllerUT.getDataRetryManager().isAnySetupRetryScheduled( + assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled( mImsCellularDataProfile, AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).isFalse(); // But DNC should re-evaluate unsatisfied request and setup IMS again. @@ -2799,8 +2885,8 @@ public class DataNetworkControllerTest extends TelephonyTest { // There should be only one attempt, and no retry should happen because it's a permanent // failure. verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(), - any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(), - any(), any(), anyBoolean(), any(Message.class)); + eq(mGeneralPurposeDataProfile), anyBoolean(), anyBoolean(), anyInt(), any(), + anyInt(), any(), any(), anyBoolean(), any(Message.class)); Mockito.clearInvocations(mMockedWwanDataServiceManager); mDataNetworkControllerUT.addNetworkRequest( @@ -2880,6 +2966,35 @@ public class DataNetworkControllerTest extends TelephonyTest { .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN); } + @Test + public void testHandoverDataNetworkNetworkSuggestedRetryTimerDataThrottled() throws Exception { + testSetupImsDataNetwork(); + + DataNetwork network = getDataNetworks().get(0); + setFailedSetupDataResponse(mMockedWlanDataServiceManager, + DataFailCause.HANDOVER_FAILED, 10000, true); + updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + + // Verify retry scheduled on this network + assertThat(mDataNetworkControllerUT.getDataRetryManager() + .isAnyHandoverRetryScheduled(network)).isTrue(); + // Verify the data profile is throttled on WLAN + assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled( + network.getDataProfile(), AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).isTrue(); + + // Test even if network disconnected, the throttle status should remain + network.tearDown(DataNetwork.TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED); + processAllFutureMessages(); + + // Verify retry is cleared on this network + assertThat(mDataNetworkControllerUT.getDataRetryManager() + .isAnyHandoverRetryScheduled(network)).isFalse(); + // Verify the data profile is still throttled + assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled( + network.getDataProfile(), AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).isTrue(); + } + @Test public void testTacChangesClearThrottlingAndRetryHappens() throws Exception { testSetupDataNetworkNetworkSuggestedRetryTimerDataThrottled(); @@ -2893,7 +3008,7 @@ public class DataNetworkControllerTest extends TelephonyTest { processAllFutureMessages(); // TAC changes should clear the already-scheduled retry and throttling. - assertThat(mDataNetworkControllerUT.getDataRetryManager().isAnySetupRetryScheduled( + assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled( mImsCellularDataProfile, AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).isFalse(); // But DNC should re-evaluate unsatisfied request and setup IMS again. @@ -3114,6 +3229,73 @@ public class DataNetworkControllerTest extends TelephonyTest { verifyAllDataDisconnected(); } + @Test + public void testSetPreferredDataProfileMultiInternetDataProfile() throws Exception { + // No preferred data profile in the beginning + doReturn(false).when(mDataProfileManager).canPreferredDataProfileSatisfy( + any(NetworkRequestList.class)); + + testSetupDataNetwork(); + + // Verify this network still alive after evaluation + mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS) + .sendToTarget(); + processAllMessages(); + + verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile); + + // Network connected, became preferred data profile + doAnswer(invocation -> { + NetworkRequestList networkRequests = + (NetworkRequestList) invocation.getArguments()[0]; + return networkRequests.stream() + .allMatch(request -> request.canBeSatisfiedBy(mGeneralPurposeDataProfile)); + }).when(mDataProfileManager).canPreferredDataProfileSatisfy( + any(NetworkRequestList.class)); + doReturn(true).when(mDataProfileManager) + .isDataProfilePreferred(mGeneralPurposeDataProfile); + + // 1. Test DUN | DEFAULT data profile is compatible with preferred default internet + mDataNetworkControllerUT.addNetworkRequest( + createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_DUN)); + setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 2); + processAllMessages(); + + // Verify both DUN and preferred default network are alive + verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile); + verifyConnectedNetworkHasDataProfile(mTetheringDataProfile); + + // Verify this network still alive after evaluation + mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS) + .sendToTarget(); + processAllMessages(); + + verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile); + verifyConnectedNetworkHasDataProfile(mTetheringDataProfile); + + // 2. Test tear down when user changes preferred data profile + doAnswer(invocation -> { + NetworkRequestList networkRequests = + (NetworkRequestList) invocation.getArguments()[0]; + return networkRequests.stream() + .allMatch(request -> request.canBeSatisfiedBy( + mGeneralPurposeDataProfileAlternative)); + }).when(mDataProfileManager).canPreferredDataProfileSatisfy( + any(NetworkRequestList.class)); + doReturn(true).when(mDataProfileManager) + .isDataProfilePreferred(mGeneralPurposeDataProfileAlternative); + doReturn(false).when(mDataProfileManager) + .isDataProfilePreferred(mGeneralPurposeDataProfile); + + mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS) + .sendToTarget(); + processAllMessages(); + + List dataNetworks = getDataNetworks(); + assertThat(dataNetworks).hasSize(1); + verifyConnectedNetworkHasDataProfile(mTetheringDataProfile); + } + @Test public void testDataDisableNotAllowingBringingUpTetheringNetwork() throws Exception { // User data disabled @@ -3181,9 +3363,13 @@ public class DataNetworkControllerTest extends TelephonyTest { } @Test public void testNonVoPSNoIMSSetup() throws Exception { - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); @@ -3203,9 +3389,13 @@ public class DataNetworkControllerTest extends TelephonyTest { carrierConfigChanged(); // VOPS not supported - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); @@ -3216,9 +3406,13 @@ public class DataNetworkControllerTest extends TelephonyTest { verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS); // VoPS supported - dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); @@ -3398,11 +3592,11 @@ public class DataNetworkControllerTest extends TelephonyTest { verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_DUN); mDataNetworkControllerUT.addNetworkRequest( - createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET)); + createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)); processAllFutureMessages(); // Lower priority network should not trump the higher priority network. verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_DUN); - verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE); // Now send a higher priority network request TelephonyNetworkRequest fotaRequest = createNetworkRequest( @@ -3574,38 +3768,63 @@ public class DataNetworkControllerTest extends TelephonyTest { @Test public void testHandoverDataNetworkOos() throws Exception { - ServiceState ss = new ServiceState(); - ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() - .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) - .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE) - .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) - .setDomain(NetworkRegistrationInfo.DOMAIN_PS) - .build()); - - ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() - .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) - .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN) - .setRegistrationState( - NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING) - .setDomain(NetworkRegistrationInfo.DOMAIN_PS) - .build()); - - ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() - .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) - .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE) - .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) - .setDomain(NetworkRegistrationInfo.DOMAIN_CS) - .build()); - processServiceStateRegStateForTest(ss); - doReturn(ss).when(mSST).getServiceState(); - doReturn(ss).when(mPhone).getServiceState(); + // Config delay IMS tear down enabled + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, + true); + carrierConfigChanged(); - mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget(); - processAllMessages(); + // VoPS supported + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); + serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME /*data*/, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME /*voice*/, + NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING /*iwlan*/, + dsri); testSetupImsDataNetwork(); + DataNetwork dataNetwork = getDataNetworks().get(0); + + // 1. Active VoPS call, mock target IWLAN OOS, should schedule retry + doReturn(PhoneConstants.State.RINGING).when(mCT).getState(); updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + // Process DRM event to schedule retry + processAllMessages(); + + // Verify scheduled new handover retry + assertTrue(mDataNetworkControllerUT.getDataRetryManager() + .isAnyHandoverRetryScheduled(dataNetwork)); + // Verify the network wasn't torn down + verify(mMockedWwanDataServiceManager, never()).deactivateDataCall(anyInt(), + eq(DataService.REQUEST_REASON_NORMAL), any(Message.class)); + + // Get the scheduled retry + Field field = DataRetryManager.class.getDeclaredField("mDataRetryEntries"); + field.setAccessible(true); + DataRetryManager.DataHandoverRetryEntry dataRetryEntry = + (DataRetryManager.DataHandoverRetryEntry) ((List) + field.get(mDataNetworkControllerUT.getDataRetryManager())).get(0); + + // Process the retry + moveTimeForward(1000 /*The retry delay of the first attempt*/); + processAllMessages(); + + // Verify the previous retry is set to FAILED + assertEquals(DataRetryManager.DataRetryEntry.RETRY_STATE_FAILED, dataRetryEntry.getState()); + // Verify a new retry is scheduled + assertTrue(mDataNetworkControllerUT.getDataRetryManager() + .isAnyHandoverRetryScheduled(dataNetwork)); + + // 2. Normal case (call ended), should tear down + doReturn(PhoneConstants.State.IDLE).when(mCT).getState(); + mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget(); + processAllFutureMessages(); // Verify that handover is not performed. verify(mMockedWlanDataServiceManager, never()).setupDataCall(anyInt(), @@ -3613,7 +3832,7 @@ public class DataNetworkControllerTest extends TelephonyTest { eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), any(), anyBoolean(), any(Message.class)); - // IMS network should be torn down. + // Verify IMS network should be torn down. verifyAllDataDisconnected(); } @@ -3685,9 +3904,13 @@ public class DataNetworkControllerTest extends TelephonyTest { public void testHandoverDataNetworkNonVops() throws Exception { ServiceState ss = new ServiceState(); - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) @@ -3748,9 +3971,13 @@ public class DataNetworkControllerTest extends TelephonyTest { ServiceState ss = new ServiceState(); - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) @@ -3808,9 +4035,13 @@ public class DataNetworkControllerTest extends TelephonyTest { public void testNonMmtelImsHandoverDataNetworkNonVops() throws Exception { ServiceState ss = new ServiceState(); - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) @@ -3876,9 +4107,13 @@ public class DataNetworkControllerTest extends TelephonyTest { ServiceState ss = new ServiceState(); // VoPS network - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) @@ -3923,9 +4158,13 @@ public class DataNetworkControllerTest extends TelephonyTest { ss = new ServiceState(); // Non VoPS network - dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) @@ -3961,7 +4200,7 @@ public class DataNetworkControllerTest extends TelephonyTest { mCarrierConfig.putBoolean(CarrierConfigManager.Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, false); carrierConfigChanged(); - mDataNetworkControllerUT.obtainMessage(16/*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/) + mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS) .sendToTarget(); processAllMessages(); @@ -3977,9 +4216,13 @@ public class DataNetworkControllerTest extends TelephonyTest { carrierConfigChanged(); // VoPS supported - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); @@ -3991,9 +4234,13 @@ public class DataNetworkControllerTest extends TelephonyTest { doReturn(PhoneConstants.State.OFFHOOK).when(mCT).getState(); - dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); @@ -4003,7 +4250,7 @@ public class DataNetworkControllerTest extends TelephonyTest { // Call ends doReturn(PhoneConstants.State.IDLE).when(mCT).getState(); - mDataNetworkControllerUT.obtainMessage(18/*EVENT_VOICE_CALL_ENDED*/).sendToTarget(); + mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget(); processAllMessages(); verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS); @@ -4019,16 +4266,18 @@ public class DataNetworkControllerTest extends TelephonyTest { }).when(mMockedWwanDataServiceManager).deactivateDataCall( anyInt(), anyInt(), any(Message.class)); // Simulate old devices - doReturn(RIL.RADIO_HAL_VERSION_1_6).when(mPhone).getHalVersion(); + doReturn(RIL.RADIO_HAL_VERSION_1_6).when(mPhone).getHalVersion(HAL_SERVICE_DATA); testSetupDataNetwork(); - mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/, + mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED, TelephonyManager.SIM_STATE_ABSENT, 0).sendToTarget(); processAllMessages(); verifyAllDataDisconnected(); verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false)); verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected(); + verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged( + eq(DataCallResponse.LINK_STATUS_INACTIVE)); } @Test 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 1650148a0b9fa7cdb4cf23c4b6b5970d9e4417b3..b85081f1c6d341951c749d8aee2daa54a711113d 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java @@ -202,30 +202,8 @@ public class DataNetworkTest extends TelephonyTest { doAnswer(invocation -> { final Message msg = (Message) invocation.getArguments()[10]; - DataCallResponse response = new DataCallResponse.Builder() - .setCause(0) - .setRetryDurationMillis(-1L) - .setId(cid) - .setLinkStatus(2) - .setProtocolType(ApnSetting.PROTOCOL_IPV4V6) - .setInterfaceName("ifname") - .setAddresses(Arrays.asList( - new LinkAddress(InetAddresses.parseNumericAddress(IPV4_ADDRESS), 32), - new LinkAddress(IPV6_ADDRESS + "/64"))) - .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.3"), - InetAddresses.parseNumericAddress("fd00:976a::9"))) - .setGatewayAddresses(Arrays.asList( - InetAddresses.parseNumericAddress("10.0.2.15"), - InetAddresses.parseNumericAddress("fe80::2"))) - .setPcscfAddresses(Arrays.asList( - InetAddresses.parseNumericAddress("fd00:976a:c305:1d::8"), - InetAddresses.parseNumericAddress("fd00:976a:c202:1d::7"), - InetAddresses.parseNumericAddress("fd00:976a:c305:1d::5"))) - .setMtuV4(1234) - .setPduSessionId(1) - .setQosBearerSessions(new ArrayList<>()) - .setTrafficDescriptors(tds) - .build(); + DataCallResponse response = createDataCallResponse( + cid, DataCallResponse.LINK_STATUS_ACTIVE, tds); msg.getData().putParcelable("data_call_response", response); msg.arg1 = DataServiceCallback.RESULT_SUCCESS; msg.sendToTarget(); @@ -235,6 +213,34 @@ public class DataNetworkTest extends TelephonyTest { any(Message.class)); } + private DataCallResponse createDataCallResponse(int cid, int linkStatus, + List tds) { + return new DataCallResponse.Builder() + .setCause(0) + .setRetryDurationMillis(-1L) + .setId(cid) + .setLinkStatus(linkStatus) + .setProtocolType(ApnSetting.PROTOCOL_IPV4V6) + .setInterfaceName("ifname") + .setAddresses(Arrays.asList( + new LinkAddress(InetAddresses.parseNumericAddress(IPV4_ADDRESS), 32), + new LinkAddress(IPV6_ADDRESS + "/64"))) + .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.3"), + InetAddresses.parseNumericAddress("fd00:976a::9"))) + .setGatewayAddresses(Arrays.asList( + InetAddresses.parseNumericAddress("10.0.2.15"), + InetAddresses.parseNumericAddress("fe80::2"))) + .setPcscfAddresses(Arrays.asList( + InetAddresses.parseNumericAddress("fd00:976a:c305:1d::8"), + InetAddresses.parseNumericAddress("fd00:976a:c202:1d::7"), + InetAddresses.parseNumericAddress("fd00:976a:c305:1d::5"))) + .setMtuV4(1234) + .setPduSessionId(1) + .setQosBearerSessions(new ArrayList<>()) + .setTrafficDescriptors(tds) + .build(); + } + private void setFailedSetupDataResponse(DataServiceManager dsm, @DataServiceCallback.ResultCode int resultCode) { doAnswer(invocation -> { @@ -340,7 +346,6 @@ public class DataNetworkTest extends TelephonyTest { doReturn(true).when(mDataConfigManager).isTempNotMeteredSupportedByCarrier(); doReturn(true).when(mDataConfigManager).isNetworkTypeUnmetered( any(TelephonyDisplayInfo.class), any(ServiceState.class)); - doReturn(true).when(mDataConfigManager).isImsDelayTearDownEnabled(); doReturn(DEFAULT_MTU).when(mDataConfigManager).getDefaultMtu(); doReturn(FAKE_IMSI).when(mPhone).getSubscriberId(); doReturn(true).when(mDataNetworkController) @@ -474,9 +479,13 @@ public class DataNetworkTest extends TelephonyTest { @Test public void testCreateDataNetworkWhenOos() throws Exception { - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); // Out of service serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri); @@ -516,9 +525,13 @@ public class DataNetworkTest extends TelephonyTest { public void testRecreateAgentWhenOos() throws Exception { testCreateDataNetwork(); - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); // Out of service serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri); @@ -1366,9 +1379,13 @@ public class DataNetworkTest extends TelephonyTest { @Test public void testMovingToNonVops() throws Exception { - DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_SUPPORTED)); + DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_SUPPORTED)) + .build(); serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); testCreateImsDataNetwork(); @@ -1376,9 +1393,13 @@ public class DataNetworkTest extends TelephonyTest { assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability( NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue(); - dsri = new DataSpecificRegistrationInfo(8, false, true, true, - new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, - LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)); + dsri = new DataSpecificRegistrationInfo.Builder(8) + .setNrAvailable(true) + .setEnDcAvailable(true) + .setVopsSupportInfo(new LteVopsSupportInfo( + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED, + LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED)) + .build(); logd("Trigger non VoPS"); serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri); @@ -1754,4 +1775,36 @@ public class DataNetworkTest extends TelephonyTest { assertThat(mDataNetworkUT.getLinkProperties().getAllAddresses()).containsExactly( InetAddresses.parseNumericAddress(IPV4_ADDRESS)); } + + @Test + public void testLinkStatusUpdate() throws Exception { + setupDataNetwork(); + + // verify link status sent on connected + verify(mDataNetworkCallback).onConnected(eq(mDataNetworkUT)); + verify(mDataNetworkCallback).onLinkStatusChanged(eq(mDataNetworkUT), + eq(DataCallResponse.LINK_STATUS_ACTIVE)); + + // data state updated + DataCallResponse response = createDataCallResponse(123, + DataCallResponse.LINK_STATUS_DORMANT, Collections.emptyList()); + mDataNetworkUT.sendMessage(8 /*EVENT_DATA_STATE_CHANGED*/, new AsyncResult( + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, List.of(response), null)); + processAllMessages(); + + // verify link status sent on data state updated + assertThat(mDataNetworkUT.isConnected()).isTrue(); + verify(mDataNetworkCallback).onLinkStatusChanged(eq(mDataNetworkUT), + eq(DataCallResponse.LINK_STATUS_DORMANT)); + + // RIL crash + mDataNetworkUT.sendMessage(4 /*EVENT_RADIO_NOT_AVAILABLE*/); + processAllMessages(); + + // verify link status sent on disconnected + verify(mDataNetworkCallback).onDisconnected(eq(mDataNetworkUT), + eq(DataFailCause.RADIO_NOT_AVAILABLE), eq(DataNetwork.TEAR_DOWN_REASON_NONE)); + verify(mDataNetworkCallback).onLinkStatusChanged(eq(mDataNetworkUT), + eq(DataCallResponse.LINK_STATUS_INACTIVE)); + } } 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 0c01c9066e3b0abaf9823f9f4354f8ff69d43a8a..e10c2a5a12a7eb2bc7b1fa1a4187188e8fc41f87 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java @@ -75,6 +75,7 @@ import java.util.stream.Collectors; @TestableLooper.RunWithLooper public class DataProfileManagerTest extends TelephonyTest { private static final String GENERAL_PURPOSE_APN = "GP_APN"; + private static final String GENERAL_PURPOSE_APN_LEGACY_RAT = "GP_APN_RAT"; private static final String GENERAL_PURPOSE_APN1 = "GP_APN1"; private static final String IMS_APN = "IMS_APN"; private static final String TETHERING_APN = "DUN_APN"; @@ -170,6 +171,41 @@ public class DataProfileManagerTest extends TelephonyTest { -1, // skip_464xlat 0 // always_on }, + // default internet data profile for RAT CDMA, to test update preferred data profile + new Object[]{ + 9, // id + PLMN, // numeric + GENERAL_PURPOSE_APN_LEGACY_RAT, // name + GENERAL_PURPOSE_APN_LEGACY_RAT, // apn + "", // proxy + "", // port + "", // mmsc + "", // mmsproxy + "", // mmsport + "", // user + "", // password + -1, // authtype + "default,supl,mms,ia", // types + "IPV4V6", // protocol + "IPV4V6", // roaming_protocol + 1, // carrier_enabled + 0, // profile_id + 1, // modem_cognitive + 0, // max_conns + 0, // wait_time + 0, // max_conns_time + 0, // mtu + 1280, // mtu_v4 + 1280, // mtu_v6 + "", // mvno_type + "", // mnvo_match_data + TelephonyManager.NETWORK_TYPE_BITMASK_CDMA, // network_type_bitmask + 0, // lingering_network_type_bitmask + DEFAULT_APN_SET_ID, // apn_set_id + -1, // carrier_id + -1, // skip_464xlat + 0 // always_on + }, new Object[]{ 2, // id PLMN, // numeric @@ -777,13 +813,51 @@ public class DataProfileManagerTest extends TelephonyTest { assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN); dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime()); dataProfile.setPreferred(true); - mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(dataProfile)); + DataNetwork internetNetwork = Mockito.mock(DataNetwork.class); + doReturn(dataProfile).when(internetNetwork).getDataProfile(); + doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr))) + .when(internetNetwork).getAttachedNetworkRequestList(); + mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork)); processAllMessages(); - // See if the same one can be returned. + // Test See if the same one can be returned. dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest( tnr, TelephonyManager.NETWORK_TYPE_LTE, false); assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN); + assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue(); + + // Test Another default internet network connected due to RAT changed. Verify the preferred + // data profile is updated. + DataProfile legacyRatDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest( + tnr, TelephonyManager.NETWORK_TYPE_CDMA, false); + DataNetwork legacyRatInternetNetwork = Mockito.mock(DataNetwork.class); + doReturn(legacyRatDataProfile).when(legacyRatInternetNetwork).getDataProfile(); + doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr))) + .when(legacyRatInternetNetwork).getAttachedNetworkRequestList(); + mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of( + // Because internetNetwork is torn down due to network type mismatch + legacyRatInternetNetwork)); + processAllMessages(); + + assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue(); + + // Test Another supl default internet network temporarily connected. Verify preferred + // doesn't change. + TelephonyNetworkRequest suplTnr = new TelephonyNetworkRequest( + new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL) + .build(), mPhone); + DataProfile suplDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest( + suplTnr, TelephonyManager.NETWORK_TYPE_LTE, false); + DataNetwork suplInternetNetwork = Mockito.mock(DataNetwork.class); + doReturn(suplDataProfile).when(suplInternetNetwork).getDataProfile(); + doReturn(new DataNetworkController.NetworkRequestList(List.of(suplTnr))) + .when(suplInternetNetwork).getAttachedNetworkRequestList(); + mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of( + legacyRatInternetNetwork, suplInternetNetwork)); + processAllMessages(); + + assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue(); } @Test @@ -1065,11 +1139,12 @@ public class DataProfileManagerTest extends TelephonyTest { tnr, TelephonyManager.NETWORK_TYPE_LTE, false); assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN); dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime()); - mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(dataProfile)); + DataNetwork internetNetwork = Mockito.mock(DataNetwork.class); + doReturn(dataProfile).when(internetNetwork).getDataProfile(); + mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork)); processAllMessages(); // After internet connected, preferred APN should be set - assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue(); assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue(); // APN reset @@ -1078,7 +1153,6 @@ public class DataProfileManagerTest extends TelephonyTest { processAllMessages(); // preferred APN should set to be the last data profile that succeeded for internet setup - assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue(); assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue(); // Test user selected a bad data profile, expects to adopt the last data profile that @@ -1088,7 +1162,6 @@ public class DataProfileManagerTest extends TelephonyTest { mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget(); processAllMessages(); - assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue(); assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isFalse(); // APN reset, preferred APN should set to be the last data profile that succeeded for @@ -1097,11 +1170,10 @@ public class DataProfileManagerTest extends TelephonyTest { mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget(); processAllMessages(); - assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue(); assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue(); // Test removed data profile(user created after reset) shouldn't show up - mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(dataProfile)); + mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork)); processAllMessages(); //APN reset and removed GENERAL_PURPOSE_APN from APN DB mPreferredApnId = -1; @@ -1110,7 +1182,6 @@ public class DataProfileManagerTest extends TelephonyTest { processAllMessages(); // There should be no preferred APN after APN reset because last working profile is removed - assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isFalse(); assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isFalse(); // restore mApnSettingContentProvider @@ -1194,7 +1265,7 @@ public class DataProfileManagerTest extends TelephonyTest { } @Test - public void testDataProfileCompatibility() throws Exception { + public void testDataProfileCompatibility() { DataProfile enterpriseDataProfile = new DataProfile.Builder() .setTrafficDescriptor(new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(TrafficDescriptor.OsAppId.ANDROID_OS_ID, 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 25ca7b168b3ef3156ae438f5ec7ca9b233eb236e..84b3302ba0ceb3d30ccb91a2486ea99c2a52020f 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java @@ -24,12 +24,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; 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.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Intent; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.os.AsyncResult; @@ -130,6 +135,7 @@ public class DataRetryManagerTest extends TelephonyTest { .build(); // Mocked classes + private AlarmManager mAlarmManager; private DataRetryManagerCallback mDataRetryManagerCallbackMock; private DataRetryManager mDataRetryManagerUT; @@ -147,6 +153,7 @@ public class DataRetryManagerTest extends TelephonyTest { ((Runnable) invocation.getArguments()[0]).run(); return null; }).when(mDataRetryManagerCallbackMock).invokeFromExecutor(any(Runnable.class)); + mAlarmManager = Mockito.mock(AlarmManager.class); SparseArray mockedDataServiceManagers = new SparseArray<>(); mockedDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, mMockedWwanDataServiceManager); @@ -166,6 +173,8 @@ public class DataRetryManagerTest extends TelephonyTest { dataNetworkControllerCallbackCaptor.capture()); mDataNetworkControllerCallback = dataNetworkControllerCallbackCaptor.getValue(); + replaceInstance(DataRetryManager.class, "mAlarmManager", + mDataRetryManagerUT, mAlarmManager); logd("DataRetryManagerTest -Setup!"); } @@ -356,14 +365,28 @@ public class DataRetryManagerTest extends TelephonyTest { .setSetupRetryType(1) .build(); mDataRetryEntries.addAll(List.of(scheduledRetry1, scheduledRetry2)); + // Suppose we set the data profile as permanently failed. + mDataProfile3.getApnSetting().setPermanentFailed(true); + + DataProfile dataProfile3ReconstructedFromModem = new DataProfile.Builder() + .setApnSetting(new ApnSetting.Builder() + .setEntryName("some_fake_ims") + .setApnName("fake_ims") + .setApnTypeBitmask(ApnSetting.TYPE_IMS) + .setProtocol(ApnSetting.PROTOCOL_IPV6) + .setRoamingProtocol(ApnSetting.PROTOCOL_IP) + .build()) + .build(); // unthrottle the data profile, expect previous retries of the same transport is cancelled mDataRetryManagerUT.obtainMessage(6/*EVENT_DATA_PROFILE_UNTHROTTLED*/, - new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, mDataProfile3, null)) + new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + dataProfile3ReconstructedFromModem, null)) .sendToTarget(); processAllMessages(); // check unthrottle + assertThat(mDataProfile3.getApnSetting().getPermanentFailed()).isFalse(); ArgumentCaptor> throttleStatusCaptor = ArgumentCaptor.forClass(List.class); verify(mDataRetryManagerCallbackMock).onThrottleStatusChanged( @@ -497,7 +520,6 @@ public class DataRetryManagerTest extends TelephonyTest { processAllMessages(); NetworkRequest request = new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(); TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone); @@ -587,6 +609,16 @@ public class DataRetryManagerTest extends TelephonyTest { // Verify there is no retry. verify(mDataRetryManagerCallbackMock, never()) .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class)); + + // 4th failed on a different transport and retry. + mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN, networkRequestList, 123, + DataCallResponse.RETRY_DURATION_UNDEFINED); + processAllFutureMessages(); + + // Verify retry occurs + verify(mDataRetryManagerCallbackMock) + .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class)); } @Test @@ -739,6 +771,43 @@ public class DataRetryManagerTest extends TelephonyTest { assertThat(rule.getRetryIntervalsMillis()).containsExactly(5000L); } + @Test + public void testDataRetryLongTimer() { + // Rule requires a long timer + DataSetupRetryRule retryRule = new DataSetupRetryRule( + "capabilities=internet, retry_interval=120000, maximum_retries=2"); + doReturn(Collections.singletonList(retryRule)).when(mDataConfigManager) + .getDataSetupRetryRules(); + mDataConfigManagerCallback.onCarrierConfigChanged(); + processAllMessages(); + + NetworkRequest request = new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(); + TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone); + DataNetworkController.NetworkRequestList + networkRequestList = new DataNetworkController.NetworkRequestList(tnr); + mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, networkRequestList, 2253, + DataCallResponse.RETRY_DURATION_UNDEFINED); + processAllFutureMessages(); + + // Verify scheduled via Alarm Manager + ArgumentCaptor pendingIntentArgumentCaptor = + ArgumentCaptor.forClass(PendingIntent.class); + verify(mAlarmManager).setAndAllowWhileIdle(anyInt(), anyLong(), + pendingIntentArgumentCaptor.capture()); + + // Verify starts retry attempt after receiving intent + PendingIntent pendingIntent = pendingIntentArgumentCaptor.getValue(); + Intent intent = pendingIntent.getIntent(); + mContext.sendBroadcast(intent); + processAllFutureMessages(); + + verify(mDataRetryManagerCallbackMock) + .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class)); + } + @Test public void testDataHandoverRetryInvalidRulesFromString() { assertThrows(IllegalArgumentException.class, 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 7a1aecaa823aa125526fa0cb2716f9bc7b9a8679..f7525c1e5259d874a21f979229652d1ba128de31 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -64,9 +63,6 @@ public class DataSettingsManagerTest extends TelephonyTest { mBundle = mContextFixture.getCarrierConfigBundle(); doReturn(true).when(mDataConfigManager).isConfigCarrierSpecific(); - doReturn("").when(mSubscriptionController).getEnabledMobileDataPolicies(anyInt()); - doReturn(true).when(mSubscriptionController).setEnabledMobileDataPolicies( - anyInt(), anyString()); doReturn(new SubscriptionInfoInternal.Builder().setId(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); @@ -108,13 +104,8 @@ public class DataSettingsManagerTest extends TelephonyTest { processAllMessages(); ArgumentCaptor stringArgumentCaptor = ArgumentCaptor.forClass(String.class); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, times(2)) - .setEnabledMobileDataPolicies(anyInt(), stringArgumentCaptor.capture()); - } else { - verify(mSubscriptionController, times(2)) - .setEnabledMobileDataPolicies(anyInt(), stringArgumentCaptor.capture()); - } + verify(mSubscriptionManagerService, times(2)) + .setEnabledMobileDataPolicies(anyInt(), stringArgumentCaptor.capture()); assertEquals("1,2", stringArgumentCaptor.getValue()); } 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 5bf66c53dc2c59db4c58f047904a4699362e6d4b..35d3b9268ad74f4f86e83f2246875d1209e4e112 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java @@ -19,7 +19,6 @@ package com.android.internal.telephony.data; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -29,7 +28,6 @@ import static org.mockito.Mockito.verify; import android.net.NetworkAgent; import android.telephony.Annotation.ValidationStatus; import android.telephony.CarrierConfigManager; -import android.telephony.data.DataProfile; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -71,14 +69,10 @@ public class DataStallRecoveryManagerTest extends TelephonyTest { .getDataStallRecoveryShouldSkipArray(); doReturn(true).when(mDataNetworkController).isInternetDataAllowed(); - doAnswer( - invocation -> { - ((Runnable) invocation.getArguments()[0]).run(); - return null; - }) - .when(mDataStallRecoveryManagerCallback) - .invokeFromExecutor(any(Runnable.class)); - doReturn("").when(mSubscriptionController).getEnabledMobileDataPolicies(anyInt()); + doAnswer(invocation -> { + ((Runnable) invocation.getArguments()[0]).run(); + return null; + }).when(mDataStallRecoveryManagerCallback).invokeFromExecutor(any(Runnable.class)); mDataStallRecoveryManager = new DataStallRecoveryManager( @@ -117,7 +111,7 @@ public class DataStallRecoveryManagerTest extends TelephonyTest { dataNetworkControllerCallbackCaptor.getValue(); if (isConnected) { - List dataprofile = new ArrayList(); + List dataprofile = new ArrayList<>(); dataNetworkControllerCallback.onInternetDataNetworkConnected(dataprofile); } else { dataNetworkControllerCallback.onInternetDataNetworkDisconnected(); @@ -351,54 +345,6 @@ public class DataStallRecoveryManagerTest extends TelephonyTest { } } - @Test - public void testNextRecoveryAfterSkippingUnderPoorSignal() throws Exception { - // Test to validate if the next recovery action is performed in good signal - // soon after skipping the recovery action under poor signal condition - sendOnInternetDataNetworkCallback(true); - mDataStallRecoveryManager.setRecoveryAction(1); - doReturn(1).when(mSignalStrength).getLevel(); - doReturn(mSignalStrength).when(mPhone).getSignalStrength(); - doReturn(PhoneConstants.State.IDLE).when(mPhone).getState(); - - logd("Sending validation failed callback"); - sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID); - processAllMessages(); - moveTimeForward(101); - - // verify skipping recovery action under poor signal condition - assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(1); - - // Set the signal condition to good - doReturn(3).when(mSignalStrength).getLevel(); - - logd("Sending validation failed callback"); - sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID); - processAllMessages(); - moveTimeForward(101); - - // verify next recovery action is performed under good signal condition - assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(3); - } - - @Test - public void testDoNotRecoveryForAlwaysInvalidNetwork() throws Exception { - // Test to verify that recovery action is not performed for always invalid network - // In some lab testing scenarios, n/w validation always remain invalid. - sendOnInternetDataNetworkCallback(false); - doReturn(mSignalStrength).when(mPhone).getSignalStrength(); - doReturn(PhoneConstants.State.IDLE).when(mPhone).getState(); - mDataStallRecoveryManager - .setRecoveryAction(DataStallRecoveryManager.RECOVERY_ACTION_GET_DATA_CALL_LIST); - - logd("Sending validation failed callback"); - sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID); - processAllFutureMessages(); - moveTimeForward(101); - - assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(0); - } - @Test public void testStartTimeNotZero() throws Exception { sendOnInternetDataNetworkCallback(false); 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 f0a73a9d64ac78eaf5bd012d7489e54e076df9c6..bc691f62b8c787fa70e695d92d116eb2e93cb3c2 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java @@ -199,7 +199,7 @@ public class PhoneSwitcherTest extends TelephonyTest { initialize(); // verify nothing has been done while there are no inputs - assertFalse("data allowed initially", mDataAllowed[0]); + assertTrue("data should be always allowed for emergency", mDataAllowed[0]); assertFalse("data allowed initially", mDataAllowed[1]); NetworkRequest internetNetworkRequest = addInternetNetworkRequest(null, 50); @@ -305,7 +305,7 @@ public class PhoneSwitcherTest extends TelephonyTest { processAllMessages(); verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong()); clearInvocations(mActivePhoneSwitchHandler); - assertFalse("data allowed", mDataAllowed[0]); + assertTrue("data not allowed", mDataAllowed[0]); assertFalse("data allowed", mDataAllowed[1]); // 6 gain subscription-specific request @@ -349,7 +349,7 @@ public class PhoneSwitcherTest extends TelephonyTest { processAllMessages(); verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong()); clearInvocations(mActivePhoneSwitchHandler); - assertFalse("data allowed", mDataAllowed[0]); + assertTrue("data not allowed", mDataAllowed[0]); assertFalse("data allowed", mDataAllowed[1]); // 10 don't switch phones when in emergency mode @@ -562,7 +562,7 @@ public class PhoneSwitcherTest extends TelephonyTest { @Test @SmallTest - public void testAutoDataSwitchRetry() 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. @@ -590,10 +590,9 @@ public class PhoneSwitcherTest extends TelephonyTest { @Test @SmallTest - public void testAutoDataSwitchSetNotification() throws Exception { + public void testAutoDataSwitch_setNotification() throws Exception { SubscriptionInfo mockedInfo = mock(SubscriptionInfo.class); doReturn(false).when(mockedInfo).isOpportunistic(); - doReturn(mockedInfo).when(mSubscriptionController).getSubscriptionInfo(anyInt()); doReturn(mockedInfo).when(mSubscriptionManagerService).getSubscriptionInfo(anyInt()); initialize(); // Phone 0 has sub 1, phone 1 has sub 2. @@ -603,37 +602,59 @@ public class PhoneSwitcherTest extends TelephonyTest { setDefaultDataSubId(1); testAutoSwitchToSecondarySucceed(); - clearInvocations(mSubscriptionController); clearInvocations(mSubscriptionManagerService); Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null, null)) .sendToTarget(); processAllMessages(); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService).getSubscriptionInfo(2); - } else { - verify(mSubscriptionController).getSubscriptionInfo(2); - } - + verify(mSubscriptionManagerService).getSubscriptionInfo(2); // switch back to primary - clearInvocations(mSubscriptionController); clearInvocations(mSubscriptionManagerService); Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(0, null, null)) .sendToTarget(); processAllMessages(); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, never()).getSubscriptionInfo(1); - } else { - verify(mSubscriptionController, never()).getSubscriptionInfo(1); - } + verify(mSubscriptionManagerService, never()).getSubscriptionInfo(1); Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null, null)) .sendToTarget(); processAllMessages(); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, never()).getSubscriptionInfo(2); - } else { - verify(mSubscriptionController, never()).getSubscriptionInfo(2); - } + verify(mSubscriptionManagerService, never()).getSubscriptionInfo(2); + } + + @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(); + processAllFutureMessages(); + + verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false), + eq(mPhoneSwitcherUT.mValidationCallback)); + mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2); + 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 } /** @@ -732,7 +753,6 @@ public class PhoneSwitcherTest extends TelephonyTest { setSlotIndexToSubId(1, 2); setDefaultDataSubId(1); - doReturn(true).when(mSubscriptionController).isOpportunistic(2); doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService .getSubscriptionInfoInternal(2)).setOpportunistic(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2); @@ -779,7 +799,6 @@ public class PhoneSwitcherTest extends TelephonyTest { setSlotIndexToSubId(0, 1); setSlotIndexToSubId(1, 2); // single visible sub, as the other one is CBRS - doReturn(new int[1]).when(mSubscriptionController).getActiveSubIdList(true); doReturn(new int[1]).when(mSubscriptionManagerService).getActiveSubIdList(true); setDefaultDataSubId(1); @@ -836,7 +855,6 @@ public class PhoneSwitcherTest extends TelephonyTest { setDefaultDataSubId(1); clearInvocations(mCellularNetworkValidator); - doReturn(new int[1]).when(mSubscriptionController).getActiveSubIdList(true); doReturn(new int[1]).when(mSubscriptionManagerService).getActiveSubIdList(true); prepareIdealAutoSwitchCondition(); processAllFutureMessages(); @@ -890,7 +908,6 @@ public class PhoneSwitcherTest extends TelephonyTest { new TelephonyNetworkRequest(mmsRequest, mPhone), 1)); // Set sub 2 as preferred sub should make phone 1 preferredDataModem - doReturn(true).when(mSubscriptionController).isOpportunistic(2); doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService .getSubscriptionInfoInternal(2)).setOpportunistic(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2); @@ -952,8 +969,6 @@ public class PhoneSwitcherTest extends TelephonyTest { doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported(); initialize(); - // Mark sub 2 as opportunistic. - doReturn(true).when(mSubscriptionController).isOpportunistic(2); // Phone 0 has sub 1, phone 1 has sub 2. // Sub 1 is default data sub. // Both are active subscriptions are active sub, as they are in both active slots. @@ -1514,8 +1529,6 @@ public class PhoneSwitcherTest extends TelephonyTest { doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported(); initialize(); - // Mark sub 2 as opportunistic. - doReturn(true).when(mSubscriptionController).isOpportunistic(2); // Phone 0 has sub 1, phone 1 has sub 2. // Sub 1 is default data sub. // Both are active subscriptions are active sub, as they are in both active slots. @@ -1677,7 +1690,6 @@ public class PhoneSwitcherTest extends TelephonyTest { setAllPhonesInactive(); // Initialization done. - doReturn(true).when(mSubscriptionController).isOpportunistic(2); doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService .getSubscriptionInfoInternal(2)).setOpportunistic(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2); @@ -1719,7 +1731,6 @@ public class PhoneSwitcherTest extends TelephonyTest { setAllPhonesInactive(); // Initialization done. - doReturn(true).when(mSubscriptionController).isOpportunistic(2); doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService .getSubscriptionInfoInternal(2)).setOpportunistic(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2); @@ -1770,8 +1781,6 @@ public class PhoneSwitcherTest extends TelephonyTest { verify(mMockRadioConfig, times(1)).setPreferredDataModem(eq(0), any()); clearInvocations(mMockRadioConfig); - doReturn(mSubscriptionInfo).when(mSubscriptionController) - .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any()); doReturn(mSubscriptionInfo).when(mSubscriptionManagerService) .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any()); doReturn(true).when(mSubscriptionInfo).areUiccApplicationsEnabled(); @@ -1795,6 +1804,36 @@ public class PhoneSwitcherTest extends TelephonyTest { verify(mMockRadioConfig, times(1)).setPreferredDataModem(eq(0), any()); } + @Test + public void testScheduledRetryWhileMultiSimConfigChange() throws Exception { + doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported(); + initialize(); + + // Phone 0 has sub 1, phone 1 has sub 2. + // Sub 1 is default data sub. + setSlotIndexToSubId(0, 1); + setSlotIndexToSubId(1, 2); + + // for EVENT_MODEM_COMMAND_RETRY + AsyncResult res = new AsyncResult( + 1, null, new CommandException(CommandException.Error.GENERIC_FAILURE)); + Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, res).sendToTarget(); + processAllMessages(); + + // reduce count of phone + setNumPhones(1, 1); + AsyncResult result = new AsyncResult(null, 1, null); + Message.obtain(mPhoneSwitcherUT, EVENT_MULTI_SIM_CONFIG_CHANGED, result).sendToTarget(); + processAllMessages(); + + // fire retries + moveTimeForward(5000); + processAllMessages(); + + verify(mCommandsInterface0, never()).setDataAllowed(anyBoolean(), any()); + verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any()); + } + /* Private utility methods start here */ private void prepareIdealAutoSwitchCondition() { @@ -1962,9 +2001,9 @@ public class PhoneSwitcherTest extends TelephonyTest { initializeCommandInterfacesMock(); initializeTelRegistryMock(); initializeConnManagerMock(); + initializeConfigMock(); mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper()); - processAllMessages(); Field field = PhoneSwitcher.class.getDeclaredField("mDataSettingsManagerCallbacks"); field.setAccessible(true); @@ -1972,11 +2011,7 @@ public class PhoneSwitcherTest extends TelephonyTest { (Map) field.get(mPhoneSwitcherUT); - int deviceConfigValue = 10000; - field = PhoneSwitcher.class.getDeclaredField( - "mAutoDataSwitchAvailabilityStabilityTimeThreshold"); - field.setAccessible(true); - field.setInt(mPhoneSwitcherUT, deviceConfigValue); + processAllMessages(); verify(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(any(), any()); } @@ -2068,25 +2103,12 @@ public class PhoneSwitcherTest extends TelephonyTest { * network requests on PhoneSwitcher. */ private void initializeSubControllerMock() throws Exception { - doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId(); doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId(); doReturn(mDefaultDataSub).when(mMockedIsub).getDefaultDataSubId(); - doReturn(0).when(mSubscriptionController).getPhoneId(1); doReturn(0).when(mSubscriptionManagerService).getPhoneId(1); doReturn(0).when(mMockedIsub).getPhoneId(1); - doReturn(1).when(mSubscriptionController).getPhoneId(2); doReturn(1).when(mSubscriptionManagerService).getPhoneId(2); doReturn(1).when(mMockedIsub).getPhoneId(2); - doAnswer(invocation -> { - int phoneId = (int) invocation.getArguments()[0]; - if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { - return SubscriptionManager.INVALID_SUBSCRIPTION_ID; - } else if (phoneId == SubscriptionManager.DEFAULT_PHONE_INDEX) { - return mSlotIndexToSubId[0][0]; - } else { - return mSlotIndexToSubId[phoneId][0]; - } - }).when(mSubscriptionController).getSubId(anyInt()); doAnswer(invocation -> { int phoneId = (int) invocation.getArguments()[0]; @@ -2110,17 +2132,6 @@ public class PhoneSwitcherTest extends TelephonyTest { } }).when(mSubscriptionManagerService).getSubId(anyInt()); - doAnswer(invocation -> { - int subId = (int) invocation.getArguments()[0]; - - if (!SubscriptionManager.isUsableSubIdValue(subId)) return false; - - for (int i = 0; i < mSlotIndexToSubId.length; i++) { - if (mSlotIndexToSubId[i][0] == subId) return true; - } - return false; - }).when(mSubscriptionController).isActiveSubId(anyInt()); - doAnswer(invocation -> { int subId = (int) invocation.getArguments()[0]; @@ -2134,15 +2145,21 @@ public class PhoneSwitcherTest extends TelephonyTest { .setSimSlotIndex(slotIndex).setId(subId).build(); }).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); - doReturn(new int[mSlotIndexToSubId.length]).when(mSubscriptionController) - .getActiveSubIdList(true); doReturn(new int[mSlotIndexToSubId.length]).when(mSubscriptionManagerService) .getActiveSubIdList(true); } + private void initializeConfigMock() { + doReturn(mDataNetworkController).when(mPhone).getDataNetworkController(); + doReturn(mDataConfigManager).when(mDataNetworkController).getDataConfigManager(); + doReturn(1000L).when(mDataConfigManager) + .getAutoDataSwitchAvailabilityStabilityTimeThreshold(); + doReturn(7).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry(); + doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired(); + } + private void setDefaultDataSubId(int defaultDataSub) throws Exception { mDefaultDataSub = defaultDataSub; - doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId(); doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId(); if (defaultDataSub == 1) { doReturn(true).when(mPhone).isUserDataEnabled(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java index a99518dd21c834622fe4f6efb247f0649cab2fa8..5941f062fff9569b1099595a576402d3d1f631ce 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java @@ -49,7 +49,6 @@ import com.android.telephony.Rlog; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -213,8 +212,7 @@ public class TelephonyNetworkFactoryTest extends TelephonyTest { // test at this time. doAnswer(invocation -> { final NetworkCapabilities capabilitiesFilter = - mTelephonyNetworkFactoryUT.makeNetworkFilter( - mSubscriptionController.getSubId(0)); + mTelephonyNetworkFactoryUT.makeNetworkFilter(mMockedIsub.getSubId(0)); for (final TelephonyNetworkRequest request : mAllNetworkRequestSet) { final int message = request.canBeSatisfiedBy(capabilitiesFilter) ? CMD_REQUEST_NETWORK : CMD_CANCEL_REQUEST; @@ -239,7 +237,6 @@ public class TelephonyNetworkFactoryTest extends TelephonyTest { createMockedTelephonyComponents(); doReturn(false).when(mPhoneSwitcher).shouldApplyNetworkRequest(any(), anyInt()); - doReturn(subId).when(mSubscriptionController).getSubId(phoneId); doReturn(subId).when(mMockedIsub).getSubId(phoneId); // fake onSubscriptionChangedListener being triggered. mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage( @@ -302,7 +299,6 @@ public class TelephonyNetworkFactoryTest extends TelephonyTest { */ @Test @SmallTest - @Ignore("b/256052233") public void testRequests() throws Exception { mTestName = "testActive"; final int numberOfPhones = 2; @@ -314,7 +310,6 @@ public class TelephonyNetworkFactoryTest extends TelephonyTest { createMockedTelephonyComponents(); - doReturn(subId).when(mSubscriptionController).getSubId(phoneId); doReturn(subId).when(mMockedIsub).getSubId(phoneId); mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage( TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED); @@ -329,7 +324,6 @@ public class TelephonyNetworkFactoryTest extends TelephonyTest { processAllMessages(); assertEquals(1, mNetworkRequestList.size()); - doReturn(altSubId).when(mSubscriptionController).getSubId(altPhoneId); doReturn(altSubId).when(mMockedIsub).getSubId(altPhoneId); processAllMessages(); assertEquals(1, mNetworkRequestList.size()); @@ -345,7 +339,6 @@ public class TelephonyNetworkFactoryTest extends TelephonyTest { processAllMessages(); assertEquals(1, mNetworkRequestList.size()); - doReturn(unusedSubId).when(mSubscriptionController).getSubId(phoneId); doReturn(unusedSubId).when(mMockedIsub).getSubId(phoneId); mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage( TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED); @@ -356,7 +349,6 @@ public class TelephonyNetworkFactoryTest extends TelephonyTest { processAllMessages(); assertEquals(0, mNetworkRequestList.size()); - doReturn(subId).when(mSubscriptionController).getSubId(phoneId); doReturn(subId).when(mMockedIsub).getSubId(phoneId); mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage( TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED); diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ce59cc6c32ea7d18bbfb50ac8d57109bcf1f7314 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.telephony.domainselection; + +import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN; +import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN; +import static android.telephony.DomainSelectionService.SCAN_TYPE_NO_PREFERENCE; +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.os.AsyncResult; +import android.os.CancellationSignal; +import android.os.Handler; +import android.telephony.DomainSelectionService; +import android.telephony.DomainSelector; +import android.telephony.EmergencyRegResult; +import android.telephony.TransportSelectorCallback; +import android.telephony.WwanSelectorCallback; +import android.telephony.ims.ImsReasonInfo; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.CallFailCause; +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.Mockito; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DomainSelectionConnectionTest extends TelephonyTest { + + private static final String TELECOM_CALL_ID1 = "TC1"; + + private DomainSelectionController mDomainSelectionController; + private DomainSelectionConnection.DomainSelectionConnectionCallback mConnectionCallback; + private DomainSelectionConnection mDsc; + + @Before + public void setUp() throws Exception { + super.setUp(this.getClass().getSimpleName()); + + mDomainSelectionController = Mockito.mock(DomainSelectionController.class); + mConnectionCallback = + Mockito.mock(DomainSelectionConnection.DomainSelectionConnectionCallback.class); + } + + @After + public void tearDown() throws Exception { + mDsc = null; + super.tearDown(); + } + + @Test + @SmallTest + public void testTransportSelectorCallback() { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + } + + @Test + @SmallTest + public void testSelectDomain() { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + DomainSelectionService.SelectionAttributes attr = getSelectionAttributes( + mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true, + false, 0, null, null, null, null); + + mDsc.selectDomain(attr); + + verify(mDomainSelectionController).selectDomain(any(), eq(transportCallback)); + } + + @Test + @SmallTest + public void testWwanSelectorCallback() throws Exception { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + + WwanSelectorCallback wwanCallback = null; + wwanCallback = transportCallback.onWwanSelected(); + + assertNotNull(wwanCallback); + } + + @Test + @SmallTest + public void testWwanSelectorCallbackAsync() throws Exception { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + replaceInstance(DomainSelectionConnection.class, "mWwanSelectedExecutor", + mDsc, new Executor() { + public void execute(Runnable command) { + command.run(); + } + }); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + + Consumer consumer = Mockito.mock(Consumer.class); + transportCallback.onWwanSelected(consumer); + + verify(consumer).accept(any()); + } + + @Test + @SmallTest + public void testWwanSelectorCallbackOnRequestEmergencyNetworkScan() throws Exception { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + + WwanSelectorCallback wwanCallback = transportCallback.onWwanSelected(); + + assertNotNull(wwanCallback); + + replaceInstance(DomainSelectionConnection.class, "mLooper", + mDsc, mTestableLooper.getLooper()); + List preferredNetworks = new ArrayList<>(); + preferredNetworks.add(EUTRAN); + preferredNetworks.add(UTRAN); + int scanType = SCAN_TYPE_NO_PREFERENCE; + Consumer consumer = Mockito.mock(Consumer.class); + + wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, null, consumer); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(mPhone).registerForEmergencyNetworkScan( + handlerCaptor.capture(), eventCaptor.capture(), any()); + + int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN }; + + verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks), + eq(scanType), any()); + + Handler handler = handlerCaptor.getValue(); + int event = eventCaptor.getValue(); + + assertNotNull(handler); + + doReturn(new Executor() { + public void execute(Runnable r) { + r.run(); + } + }).when(mDomainSelectionController).getDomainSelectionServiceExecutor(); + EmergencyRegResult regResult = + new EmergencyRegResult(UTRAN, 0, 0, true, false, 0, 0, "", "", ""); + handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null))); + processAllMessages(); + + verify(consumer).accept(eq(regResult)); + } + + @Test + @SmallTest + public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanAndCancel() throws Exception { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + + WwanSelectorCallback wwanCallback = transportCallback.onWwanSelected(); + + assertNotNull(wwanCallback); + + replaceInstance(DomainSelectionConnection.class, "mLooper", + mDsc, mTestableLooper.getLooper()); + CancellationSignal signal = new CancellationSignal(); + wwanCallback.onRequestEmergencyNetworkScan(new ArrayList<>(), + SCAN_TYPE_NO_PREFERENCE, signal, Mockito.mock(Consumer.class)); + + verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any()); + verify(mPhone).triggerEmergencyNetworkScan(any(), anyInt(), any()); + + signal.cancel(); + + verify(mPhone).cancelEmergencyNetworkScan(eq(false), any()); + } + + @Test + @SmallTest + public void testDomainSelectorCancelSelection() throws Exception { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + + DomainSelector domainSelector = Mockito.mock(DomainSelector.class); + transportCallback.onCreated(domainSelector); + + mDsc.cancelSelection(); + + verify(domainSelector).cancelSelection(); + } + + @Test + @SmallTest + public void testDomainSelectorReselectDomain() throws Exception { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + + DomainSelector domainSelector = Mockito.mock(DomainSelector.class); + transportCallback.onCreated(domainSelector); + + DomainSelectionService.SelectionAttributes attr = getSelectionAttributes( + mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true, + false, CallFailCause.ERROR_UNSPECIFIED, null, null, null, null); + + CompletableFuture future = mDsc.reselectDomain(attr); + + assertNotNull(future); + assertFalse(future.isDone()); + + verify(domainSelector).reselectDomain(any()); + } + + @Test + @SmallTest + public void testDomainSelectorFinishSelection() throws Exception { + mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true, + mDomainSelectionController); + + TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback(); + + assertNotNull(transportCallback); + + DomainSelector domainSelector = Mockito.mock(DomainSelector.class); + transportCallback.onCreated(domainSelector); + + mDsc.finishSelection(); + + verify(domainSelector).finishSelection(); + } + + private DomainSelectionService.SelectionAttributes getSelectionAttributes( + int slotId, int subId, int selectorType, boolean isEmergency, + boolean exited, int callFailCause, String callId, String number, + ImsReasonInfo imsReasonInfo, EmergencyRegResult regResult) { + DomainSelectionService.SelectionAttributes.Builder builder = + new DomainSelectionService.SelectionAttributes.Builder( + slotId, subId, selectorType) + .setEmergency(isEmergency) + .setExitedFromAirplaneMode(exited) + .setCsDisconnectCause(callFailCause); + + if (callId != null) builder.setCallId(callId); + if (number != null) builder.setNumber(number); + if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo); + if (regResult != null) builder.setEmergencyRegResult(regResult); + + return builder.build(); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2c65b50303f83e2ca2de4e3cc645569f6af77d3c --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; +import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; + +import static com.android.internal.telephony.RIL.RADIO_HAL_VERSION_2_0; +import static com.android.internal.telephony.RIL.RADIO_HAL_VERSION_2_1; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.telephony.DomainSelectionService; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.HalVersion; +import com.android.internal.telephony.Phone; +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.Mockito; + +/** + * Unit tests for DomainSelectionResolver. + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DomainSelectionResolverTest extends TelephonyTest { + // Mock classes + private DomainSelectionController mDsController; + private DomainSelectionConnection mDsConnection; + private DomainSelectionService mDsService; + + private DomainSelectionResolver mDsResolver; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + + mDsController = Mockito.mock(DomainSelectionController.class); + mDsConnection = Mockito.mock(DomainSelectionConnection.class); + mDsService = Mockito.mock(DomainSelectionService.class); + } + + @After + public void tearDown() throws Exception { + mDsResolver = null; + mDsService = null; + mDsConnection = null; + mDsController = null; + super.tearDown(); + } + + @Test + @SmallTest + public void testGetInstance() throws IllegalStateException { + assertThrows(IllegalStateException.class, () -> { + DomainSelectionResolver.getInstance(); + }); + + DomainSelectionResolver.make(mContext, true); + DomainSelectionResolver resolver = DomainSelectionResolver.getInstance(); + + assertNotNull(resolver); + } + + @Test + @SmallTest + public void testIsDomainSelectionSupportedWhenDeviceConfigDisabled() { + setUpResolver(false, RADIO_HAL_VERSION_2_1); + + assertFalse(mDsResolver.isDomainSelectionSupported()); + } + + @Test + @SmallTest + public void testIsDomainSelectionSupportedWhenHalVersionLessThan20() { + setUpResolver(true, RADIO_HAL_VERSION_2_0); + + assertFalse(mDsResolver.isDomainSelectionSupported()); + } + + @Test + @SmallTest + public void testIsDomainSelectionSupported() { + setUpResolver(true, RADIO_HAL_VERSION_2_1); + + assertTrue(mDsResolver.isDomainSelectionSupported()); + } + + @Test + @SmallTest + public void testGetDomainSelectionConnectionWhenNotInitialized() throws Exception { + setUpResolver(true, RADIO_HAL_VERSION_2_1); + + assertThrows(IllegalStateException.class, () -> { + mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true); + }); + } + + @Test + @SmallTest + public void testGetDomainSelectionConnectionWhenPhoneNull() throws Exception { + setUpResolver(true, RADIO_HAL_VERSION_2_1); + mDsResolver.initialize(mDsService); + assertNull(mDsResolver.getDomainSelectionConnection(null, SELECTOR_TYPE_CALLING, true)); + } + + @Test + @SmallTest + public void testGetDomainSelectionConnectionWhenImsNotAvailable() throws Exception { + setUpResolver(true, RADIO_HAL_VERSION_2_1); + mDsResolver.initialize(mDsService); + when(mPhone.isImsAvailable()).thenReturn(false); + + assertNull(mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true)); + } + + @Test + @SmallTest + public void testGetDomainSelectionConnection() throws Exception { + setUpResolver(true, RADIO_HAL_VERSION_2_1); + setUpController(); + mDsResolver.initialize(mDsService); + when(mPhone.isImsAvailable()).thenReturn(true); + + assertNotNull(mDsResolver.getDomainSelectionConnection( + mPhone, SELECTOR_TYPE_CALLING, true)); + } + + private void setUpResolver(boolean deviceConfigEnabled, HalVersion halVersion) { + mDsResolver = new DomainSelectionResolver(mContext, deviceConfigEnabled); + when(mPhone.getHalVersion(eq(HAL_SERVICE_NETWORK))).thenReturn(halVersion); + } + + private void setUpController() { + mDsResolver.setDomainSelectionControllerFactory( + new DomainSelectionResolver.DomainSelectionControllerFactory() { + @Override + public DomainSelectionController create(Context context, + DomainSelectionService service) { + return mDsController; + } + }); + + when(mDsController.getDomainSelectionConnection(any(Phone.class), anyInt(), anyBoolean())) + .thenReturn(mDsConnection); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0c64b82072b303a8a1fd96153bf4de1d82a940e5 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.telephony.domainselection; + +import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN; +import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; +import static android.telephony.DisconnectCause.ERROR_UNSPECIFIED; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; +import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN; + +import static com.android.internal.telephony.PhoneConstants.DOMAIN_NON_3GPP_PS; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.telephony.DomainSelectionService; +import android.telephony.EmergencyRegResult; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.TransportSelectorCallback; +import android.telephony.WwanSelectorCallback; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.data.AccessNetworksManager; +import com.android.internal.telephony.emergency.EmergencyStateTracker; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import java.util.concurrent.CompletableFuture; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class EmergencyCallDomainSelectionConnectionTest extends TelephonyTest { + + private static final String TELECOM_CALL_ID1 = "TC1"; + + private DomainSelectionController mDomainSelectionController; + private DomainSelectionConnection.DomainSelectionConnectionCallback mConnectionCallback; + private EmergencyCallDomainSelectionConnection mEcDsc; + private AccessNetworksManager mAnm; + private TransportSelectorCallback mTransportCallback; + private EmergencyStateTracker mEmergencyStateTracker; + + @Before + public void setUp() throws Exception { + super.setUp(this.getClass().getSimpleName()); + + mDomainSelectionController = Mockito.mock(DomainSelectionController.class); + mConnectionCallback = + Mockito.mock(DomainSelectionConnection.DomainSelectionConnectionCallback.class); + mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class); + mAnm = Mockito.mock(AccessNetworksManager.class); + doReturn(mAnm).when(mPhone).getAccessNetworksManager(); + mEcDsc = new EmergencyCallDomainSelectionConnection(mPhone, + mDomainSelectionController, mEmergencyStateTracker); + mTransportCallback = mEcDsc.getTransportSelectorCallback(); + } + + @After + public void tearDown() throws Exception { + mEcDsc = null; + super.tearDown(); + } + + @Test + @SmallTest + public void testSelectDomainWifi() throws Exception { + doReturn(TRANSPORT_TYPE_WLAN).when(mAnm).getPreferredTransport(anyInt()); + replaceInstance(EmergencyCallDomainSelectionConnection.class, + "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker); + + EmergencyRegResult regResult = new EmergencyRegResult( + EUTRAN, REGISTRATION_STATE_UNKNOWN, + NetworkRegistrationInfo.DOMAIN_PS, + true, false, 0, 0, "", "", ""); + + DomainSelectionService.SelectionAttributes attr = + EmergencyCallDomainSelectionConnection.getSelectionAttributes( + mPhone.getPhoneId(), mPhone.getSubId(), false, + TELECOM_CALL_ID1, "911", 0, null, regResult); + + CompletableFuture future = + mEcDsc.createEmergencyConnection(attr, mConnectionCallback); + + assertNotNull(future); + assertFalse(future.isDone()); + + verify(mDomainSelectionController).selectDomain(any(), any()); + + mTransportCallback.onWlanSelected(true); + + assertTrue(future.isDone()); + assertEquals((long) DOMAIN_NON_3GPP_PS, (long) future.get()); + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WLAN)); + } + + @Test + @SmallTest + public void testSelectDomainCs() throws Exception { + doReturn(TRANSPORT_TYPE_WWAN).when(mAnm).getPreferredTransport(anyInt()); + replaceInstance(EmergencyCallDomainSelectionConnection.class, + "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker); + + EmergencyRegResult regResult = new EmergencyRegResult( + UTRAN, REGISTRATION_STATE_UNKNOWN, + NetworkRegistrationInfo.DOMAIN_CS, + true, false, 0, 0, "", "", ""); + + DomainSelectionService.SelectionAttributes attr = + EmergencyCallDomainSelectionConnection.getSelectionAttributes( + mPhone.getPhoneId(), mPhone.getSubId(), false, + TELECOM_CALL_ID1, "911", 0, null, regResult); + + CompletableFuture future = + mEcDsc.createEmergencyConnection(attr, mConnectionCallback); + + assertNotNull(future); + assertFalse(future.isDone()); + + verify(mDomainSelectionController).selectDomain(any(), any()); + + WwanSelectorCallback wwanCallback = null; + wwanCallback = mTransportCallback.onWwanSelected(); + + assertFalse(future.isDone()); + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN)); + + wwanCallback.onDomainSelected(DOMAIN_CS, false); + + assertTrue(future.isDone()); + assertEquals((long) DOMAIN_CS, (long) future.get()); + } + + @Test + @SmallTest + public void testSelectDomainPs() throws Exception { + doReturn(TRANSPORT_TYPE_WWAN).when(mAnm).getPreferredTransport(anyInt()); + replaceInstance(EmergencyCallDomainSelectionConnection.class, + "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker); + + EmergencyRegResult regResult = new EmergencyRegResult( + EUTRAN, REGISTRATION_STATE_UNKNOWN, + NetworkRegistrationInfo.DOMAIN_PS, + true, true, 0, 0, "", "", ""); + + DomainSelectionService.SelectionAttributes attr = + EmergencyCallDomainSelectionConnection.getSelectionAttributes( + mPhone.getPhoneId(), mPhone.getSubId(), false, + TELECOM_CALL_ID1, "911", 0, null, regResult); + + CompletableFuture future = + mEcDsc.createEmergencyConnection(attr, mConnectionCallback); + + assertNotNull(future); + assertFalse(future.isDone()); + + verify(mDomainSelectionController).selectDomain(any(), any()); + + WwanSelectorCallback wwanCallback = null; + wwanCallback = mTransportCallback.onWwanSelected(); + + assertFalse(future.isDone()); + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN)); + + wwanCallback.onDomainSelected(DOMAIN_PS, true); + + assertTrue(future.isDone()); + assertEquals((long) DOMAIN_PS, (long) future.get()); + } + + @Test + @SmallTest + public void testOnSelectionTerminated() throws Exception { + EmergencyRegResult regResult = new EmergencyRegResult( + EUTRAN, REGISTRATION_STATE_UNKNOWN, + NetworkRegistrationInfo.DOMAIN_PS, + true, true, 0, 0, "", "", ""); + + DomainSelectionService.SelectionAttributes attr = + EmergencyCallDomainSelectionConnection.getSelectionAttributes( + mPhone.getPhoneId(), mPhone.getSubId(), false, + TELECOM_CALL_ID1, "911", 0, null, regResult); + + mEcDsc.createEmergencyConnection(attr, mConnectionCallback); + mTransportCallback.onSelectionTerminated(ERROR_UNSPECIFIED); + + verify(mConnectionCallback).onSelectionTerminated(eq(ERROR_UNSPECIFIED)); + } + + @Test + @SmallTest + public void testCancelSelection() throws Exception { + mEcDsc.cancelSelection(); + verify(mAnm).unregisterForQualifiedNetworksChanged(any()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..25ccecbebc0d4bc26715e1ed2e1a3a72f221deea --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN; + +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.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.telephony.AccessNetworkConstants; +import android.telephony.DomainSelectionService; +import android.telephony.DomainSelector; +import android.telephony.NetworkRegistrationInfo; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.data.AccessNetworksManager; +import com.android.internal.telephony.emergency.EmergencyStateTracker; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.util.concurrent.CompletableFuture; + +/** + * Unit tests for EmergencySmsDomainSelectionConnection. + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class EmergencySmsDomainSelectionConnectionTest extends TelephonyTest { + private DomainSelectionController mDsController; + private DomainSelectionConnection.DomainSelectionConnectionCallback mDscCallback; + private DomainSelector mDomainSelector; + private EmergencyStateTracker mEmergencyStateTracker; + + private Handler mHandler; + private AccessNetworksManager mAnm; + private DomainSelectionService.SelectionAttributes mDsAttr; + private EmergencySmsDomainSelectionConnection mDsConnection; + + @Before + public void setUp() throws Exception { + super.setUp(this.getClass().getSimpleName()); + + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mHandler = new Handler(Looper.myLooper()); + mDsController = Mockito.mock(DomainSelectionController.class); + mDscCallback = Mockito.mock( + DomainSelectionConnection.DomainSelectionConnectionCallback.class); + mDomainSelector = Mockito.mock(DomainSelector.class); + mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class); + mAnm = Mockito.mock(AccessNetworksManager.class); + when(mPhone.getAccessNetworksManager()).thenReturn(mAnm); + + mDsConnection = new EmergencySmsDomainSelectionConnection( + mPhone, mDsController, mEmergencyStateTracker); + mDsConnection.getTransportSelectorCallback().onCreated(mDomainSelector); + mDsAttr = new DomainSelectionService.SelectionAttributes.Builder( + mPhone.getPhoneId(), mPhone.getSubId(), DomainSelectionService.SELECTOR_TYPE_SMS) + .setEmergency(true) + .build(); + } + + @After + public void tearDown() throws Exception { + mDomainSelector = null; + mDsAttr = null; + mDsConnection = null; + mDscCallback = null; + mDsController = null; + mEmergencyStateTracker = null; + super.tearDown(); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnWlanSelected() throws Exception { + when(mAnm.getPreferredTransport(anyInt())) + .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onWlanSelected(true); + processAllMessages(); + + assertTrue(future.isDone()); + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnWlanSelectedWithDifferentTransportType() throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onWlanSelected(true); + processAllMessages(); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN)); + verify(mAnm).registerForQualifiedNetworksChanged( + handlerCaptor.capture(), msgCaptor.capture()); + verify(mPhone).notifyEmergencyDomainSelected( + eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)); + + Handler handler = handlerCaptor.getValue(); + Integer msg = msgCaptor.getValue(); + handler.handleMessage(Message.obtain(handler, msg.intValue())); + processAllMessages(); + + assertTrue(future.isDone()); + verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnWlanSelectedWithDifferentTransportTypeAndImsPdn() throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onWlanSelected(false); + processAllMessages(); + + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN)); + verify(mAnm, never()).registerForQualifiedNetworksChanged(any(Handler.class), anyInt()); + verify(mPhone, never()).notifyEmergencyDomainSelected(anyInt()); + + assertTrue(future.isDone()); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnWlanSelectedWithDifferentTransportTypeWhilePreferredTransportChanged() + throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onWlanSelected(true); + // When onWlanSelected() is called again, + // it will be ignored because the change of preferred transport is in progress. + // => onEmergencyTransportChanged() is called only once. + mDsConnection.onWlanSelected(true); + processAllMessages(); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN)); + verify(mAnm).registerForQualifiedNetworksChanged( + handlerCaptor.capture(), msgCaptor.capture()); + verify(mPhone).notifyEmergencyDomainSelected( + eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)); + + Handler handler = handlerCaptor.getValue(); + Integer msg = msgCaptor.getValue(); + handler.handleMessage(Message.obtain(handler, msg.intValue())); + processAllMessages(); + + assertTrue(future.isDone()); + verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class)); + } + + @Test + @SmallTest + public void testOnWwanSelected() throws Exception { + mDsConnection.onWwanSelected(); + + verify(mEmergencyStateTracker).onEmergencyTransportChanged( + eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WWAN)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedPs() throws Exception { + when(mAnm.getPreferredTransport(anyInt())) + .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true); + processAllMessages(); + + assertTrue(future.isDone()); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedPsWithDifferentTransportType() throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true); + processAllMessages(); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mAnm).registerForQualifiedNetworksChanged( + handlerCaptor.capture(), msgCaptor.capture()); + verify(mPhone).notifyEmergencyDomainSelected( + eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)); + + Handler handler = handlerCaptor.getValue(); + Integer msg = msgCaptor.getValue(); + handler.handleMessage(Message.obtain(handler, msg.intValue())); + processAllMessages(); + + assertTrue(future.isDone()); + verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedPsWithDifferentTransportTypeAndImsPdn() throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, false); + processAllMessages(); + + verify(mAnm, never()).registerForQualifiedNetworksChanged(any(Handler.class), anyInt()); + verify(mPhone, never()).notifyEmergencyDomainSelected(anyInt()); + + assertTrue(future.isDone()); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedPsWithDifferentTransportTypeAndNotChanged() throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true); + processAllMessages(); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mAnm).registerForQualifiedNetworksChanged( + handlerCaptor.capture(), msgCaptor.capture()); + verify(mPhone).notifyEmergencyDomainSelected( + eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)); + + Handler handler = handlerCaptor.getValue(); + Integer msg = msgCaptor.getValue(); + handler.handleMessage(Message.obtain(handler, msg.intValue())); + processAllMessages(); + + assertFalse(future.isDone()); + verify(mAnm, never()).unregisterForQualifiedNetworksChanged(any(Handler.class)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedPsWithDifferentTransportTypeWhilePreferredTransportChanged() + throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true); + // When onDomainSelected() is called again with the different domain, + // it will be ignored because the change of preferred transport is in progress. + // => The domain selection result is DOMAIN_PS. + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false); + processAllMessages(); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mAnm).registerForQualifiedNetworksChanged( + handlerCaptor.capture(), msgCaptor.capture()); + verify(mPhone).notifyEmergencyDomainSelected( + eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)); + + Handler handler = handlerCaptor.getValue(); + Integer msg = msgCaptor.getValue(); + handler.handleMessage(Message.obtain(handler, msg.intValue())); + processAllMessages(); + + assertTrue(future.isDone()); + verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedCs() throws Exception { + when(mAnm.getPreferredTransport(anyInt())) + .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_CS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false); + processAllMessages(); + + assertTrue(future.isDone()); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testFinishSelection() throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true); + processAllMessages(); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mAnm).registerForQualifiedNetworksChanged( + handlerCaptor.capture(), msgCaptor.capture()); + verify(mPhone).notifyEmergencyDomainSelected( + eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)); + + mDsConnection.finishSelection(); + processAllMessages(); + + assertFalse(future.isDone()); + verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class)); + verify(mDomainSelector).cancelSelection(); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testFinishSelectionAfterDomainSelectionCompleted() throws Exception { + when(mAnm.getPreferredTransport(anyInt())).thenReturn( + AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true); + processAllMessages(); + + ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mAnm).registerForQualifiedNetworksChanged( + handlerCaptor.capture(), msgCaptor.capture()); + verify(mPhone).notifyEmergencyDomainSelected( + eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)); + + Handler handler = handlerCaptor.getValue(); + Integer msg = msgCaptor.getValue(); + handler.handleMessage(Message.obtain(handler, msg.intValue())); + processAllMessages(); + mDsConnection.finishSelection(); + + assertTrue(future.isDone()); + // This method should be invoked one time. + verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class)); + verify(mDomainSelector).finishSelection(); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0403232c4e725f1350ab7e77e397e5ac808da84e --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static android.telephony.DisconnectCause.ERROR_UNSPECIFIED; +import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.telephony.DomainSelectionService; +import android.telephony.TransportSelectorCallback; +import android.telephony.WwanSelectorCallback; +import android.telephony.ims.ImsReasonInfo; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.data.AccessNetworksManager; +import com.android.internal.telephony.domainselection.DomainSelectionConnection; +import com.android.internal.telephony.domainselection.DomainSelectionController; +import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection; + +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.concurrent.CompletableFuture; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NormalCallDomainSelectionConnectionTest extends TelephonyTest { + + private static final String TELECOM_CALL_ID1 = "TC1"; + + @Mock + private DomainSelectionController mMockDomainSelectionController; + @Mock + private DomainSelectionConnection.DomainSelectionConnectionCallback mMockConnectionCallback; + @Mock + private AccessNetworksManager mMockAccessNetworksManager; + + private TransportSelectorCallback mTransportCallback; + private NormalCallDomainSelectionConnection mNormalCallDomainSelectionConnection; + + @Before + public void setUp() throws Exception { + super.setUp(this.getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + doReturn(mMockAccessNetworksManager).when(mPhone).getAccessNetworksManager(); + mNormalCallDomainSelectionConnection = + new NormalCallDomainSelectionConnection(mPhone, mMockDomainSelectionController); + mTransportCallback = mNormalCallDomainSelectionConnection.getTransportSelectorCallback(); + } + + @After + public void tearDown() throws Exception { + mNormalCallDomainSelectionConnection = null; + super.tearDown(); + } + + @Test + @SmallTest + public void testSelectDomainWifi() throws Exception { + DomainSelectionService.SelectionAttributes attributes = + NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(), + mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null); + + CompletableFuture future = + mNormalCallDomainSelectionConnection + .createNormalConnection(attributes, mMockConnectionCallback); + + assertNotNull(future); + assertFalse(future.isDone()); + + verify(mMockDomainSelectionController).selectDomain(any(), any()); + + mTransportCallback.onWlanSelected(false); + + assertTrue(future.isDone()); + assertEquals((long) DOMAIN_PS, (long) future.get()); + } + + @Test + @SmallTest + public void testSelectDomainCs() throws Exception { + DomainSelectionService.SelectionAttributes attributes = + NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(), + mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null); + + CompletableFuture future = + mNormalCallDomainSelectionConnection + .createNormalConnection(attributes, mMockConnectionCallback); + + assertNotNull(future); + assertFalse(future.isDone()); + + verify(mMockDomainSelectionController).selectDomain(any(), any()); + + WwanSelectorCallback wwanCallback = mTransportCallback.onWwanSelected(); + + assertFalse(future.isDone()); + wwanCallback.onDomainSelected(DOMAIN_CS, false); + + assertTrue(future.isDone()); + assertEquals((long) DOMAIN_CS, (long) future.get()); + } + + @Test + @SmallTest + public void testSelectDomainPs() throws Exception { + DomainSelectionService.SelectionAttributes attributes = + NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(), + mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null); + + CompletableFuture future = + mNormalCallDomainSelectionConnection + .createNormalConnection(attributes, mMockConnectionCallback); + + assertNotNull(future); + assertFalse(future.isDone()); + + verify(mMockDomainSelectionController).selectDomain(any(), any()); + + WwanSelectorCallback wwanCallback = mTransportCallback.onWwanSelected(); + + assertFalse(future.isDone()); + wwanCallback.onDomainSelected(DOMAIN_PS, false); + + assertTrue(future.isDone()); + assertEquals((long) DOMAIN_PS, (long) future.get()); + } + + @Test + @SmallTest + public void testOnSelectionTerminated() throws Exception { + DomainSelectionService.SelectionAttributes attributes = + NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(), + mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null); + + CompletableFuture future = mNormalCallDomainSelectionConnection + .createNormalConnection(attributes, mMockConnectionCallback); + mTransportCallback.onSelectionTerminated(ERROR_UNSPECIFIED); + + verify(mMockConnectionCallback).onSelectionTerminated(eq(ERROR_UNSPECIFIED)); + assertNotNull(future); + } + + @Test + public void testGetSelectionAttributes() throws Exception { + ImsReasonInfo imsReasonInfo = new ImsReasonInfo(); + DomainSelectionService.SelectionAttributes attributes = + NormalCallDomainSelectionConnection.getSelectionAttributes(1, 2, + TELECOM_CALL_ID1, "123", false, 10, imsReasonInfo); + + assertEquals(1, attributes.getSlotId()); + assertEquals(2, attributes.getSubId()); + assertEquals(TELECOM_CALL_ID1, attributes.getCallId()); + assertEquals("123", attributes.getNumber()); + assertEquals(false, attributes.isVideoCall()); + assertEquals(false, attributes.isEmergency()); + assertEquals(SELECTOR_TYPE_CALLING, attributes.getSelectorType()); + assertEquals(10, attributes.getCsDisconnectCause()); + assertEquals(imsReasonInfo, attributes.getPsDisconnectCause()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e4afa79495338810177afea548841e4ce460a7cd --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.domainselection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.os.Handler; +import android.os.HandlerThread; +import android.telephony.DisconnectCause; +import android.telephony.DomainSelectionService; +import android.telephony.DomainSelector; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.TransportSelectorCallback; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableLooper; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.telephony.Phone; + +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.concurrent.CompletableFuture; + +/** + * Unit tests for SmsDomainSelectionConnection. + */ +@RunWith(AndroidJUnit4.class) +public class SmsDomainSelectionConnectionTest { + private static final int SLOT_ID = 0; + private static final int SUB_ID = 1; + + @Mock private Phone mPhone; + @Mock private DomainSelectionController mDsController; + @Mock private DomainSelectionConnection.DomainSelectionConnectionCallback mDscCallback; + @Mock private DomainSelector mDomainSelector; + + private Handler mHandler; + private TestableLooper mTestableLooper; + private DomainSelectionService.SelectionAttributes mDsAttr; + private SmsDomainSelectionConnection mDsConnection; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + HandlerThread handlerThread = new HandlerThread( + SmsDomainSelectionConnectionTest.class.getSimpleName()); + handlerThread.start(); + + mHandler = new Handler(handlerThread.getLooper()); + mDsConnection = new SmsDomainSelectionConnection(mPhone, mDsController); + mDsConnection.getTransportSelectorCallback().onCreated(mDomainSelector); + mDsAttr = new DomainSelectionService.SelectionAttributes.Builder( + SLOT_ID, SUB_ID, DomainSelectionService.SELECTOR_TYPE_SMS).build(); + } + + @After + public void tearDown() throws Exception { + if (mTestableLooper != null) { + mTestableLooper.destroy(); + mTestableLooper = null; + } + + if (mHandler != null) { + mHandler.getLooper().quit(); + mHandler = null; + } + + mDomainSelector = null; + mDsAttr = null; + mDsConnection = null; + mDscCallback = null; + mDsController = null; + mPhone = null; + } + + @Test + @SmallTest + public void testRequestDomainSelection() { + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + + assertNotNull(future); + verify(mDsController).selectDomain(eq(mDsAttr), any(TransportSelectorCallback.class)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnWlanSelected() throws Exception { + setUpTestableLooper(); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onWlanSelected(); + processAllMessages(); + + assertTrue(future.isDone()); + } + + @Test + @SmallTest + public void testOnSelectionTerminated() { + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + mDsConnection.onSelectionTerminated(DisconnectCause.LOCAL); + + assertFalse(future.isDone()); + verify(mDscCallback).onSelectionTerminated(eq(DisconnectCause.LOCAL)); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedPs() throws Exception { + setUpTestableLooper(); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS); + processAllMessages(); + + assertTrue(future.isDone()); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testOnDomainSelectedCs() throws Exception { + setUpTestableLooper(); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_CS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS); + processAllMessages(); + + assertTrue(future.isDone()); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testFinishSelection() throws Exception { + setUpTestableLooper(); + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS); + processAllMessages(); + mDsConnection.finishSelection(); + + verify(mDomainSelector).finishSelection(); + } + + @Test + @SmallTest + @SuppressWarnings("FutureReturnValueIgnored") + public void testCancelSelection() throws Exception { + CompletableFuture future = + mDsConnection.requestDomainSelection(mDsAttr, mDscCallback); + future.thenAcceptAsync((domain) -> { + assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain); + }, mHandler::post); + + mDsConnection.finishSelection(); + + verify(mDomainSelector).cancelSelection(); + } + + private void setUpTestableLooper() throws Exception { + mTestableLooper = new TestableLooper(mHandler.getLooper()); + } + + private void processAllMessages() { + while (!mTestableLooper.getLooper().getQueue().isIdle()) { + mTestableLooper.processAllMessages(); + } + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java index 1273148940f86a443d605eacd7288565759651d8..5653209deeffd244c31f3eec4f6b7589c8c669f2 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java @@ -261,7 +261,7 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); } @@ -284,7 +284,7 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); } public void testSameEmergencyNumberDifferentMnc() throws Exception { @@ -306,7 +306,7 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); } public void testSameEmergencyNumberDifferentCategories() throws Exception { @@ -328,7 +328,7 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); } public void testSameEmergencyNumberDifferentUrns() throws Exception { @@ -357,7 +357,7 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); } public void testSameEmergencyNumberCallRouting() throws Exception { @@ -379,7 +379,36 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + /* case 1: Check routing is not checked when comparing the same numbers. As routing will + be unknown for all numbers apart from DB. Check merge when both are not from DB then + routing value is merged from first number. */ + assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); + assertEquals(num1, EmergencyNumber.mergeSameEmergencyNumbers(num1, num2)); + + num2 = new EmergencyNumber( + "911", + "jp", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + + /* case 1: Check routing is not checked when comparing the same numbers. Check merge when + one of the number is from DB then routing value is merged from DB number. Along with + source value is masked with both*/ + assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); + + num2 = new EmergencyNumber( + "911", + "jp", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + assertEquals(num2, EmergencyNumber.mergeSameEmergencyNumbers(num1, num2)); } public void testSameEmergencyNumberDifferentSource() throws Exception { @@ -401,7 +430,7 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); } public void testSameEmergencyNumberDifferentSourceTestOrNot() throws Exception { @@ -423,7 +452,7 @@ public class EmergencyNumberTest extends TestCase { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST, EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); - assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2)); + assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false)); } public void testMergeSameNumbersInEmergencyNumberListWithDifferentSources() throws Exception { @@ -595,4 +624,246 @@ public class EmergencyNumberTest extends TestCase { assertEquals(outputNumberList, inputNumberList); } + + public void testMergeSameNumbersEmergencyNumberListByDeterminingFields() throws Exception { + List urn1 = new ArrayList<>(); + urn1.add("sos"); + + List urn2 = new ArrayList<>(); + urn2.add("sos:ambulance"); + + List inputNumberList = new ArrayList<>(); + EmergencyNumber num1 = new EmergencyNumber( + "110", + "jp", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + + EmergencyNumber num2 = new EmergencyNumber( + "110", + "jp", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, + urn1, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber num3 = new EmergencyNumber( + "911", + "us", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber num4 = new EmergencyNumber( + "911", + "us", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + + EmergencyNumber num5 = new EmergencyNumber( + "112", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + + EmergencyNumber num6 = new EmergencyNumber( + "112", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + urn1, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber num7 = new EmergencyNumber( + "108", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber num8 = new EmergencyNumber( + "108", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber num9 = new EmergencyNumber( + "102", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber num10 = new EmergencyNumber( + "102", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE, + urn1, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber num11 = new EmergencyNumber( + "100", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + + EmergencyNumber num12 = new EmergencyNumber( + "100", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + + EmergencyNumber num13 = new EmergencyNumber( + "200", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + + EmergencyNumber num14 = new EmergencyNumber( + "200", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + inputNumberList.add(num1); + inputNumberList.add(num2); + inputNumberList.add(num3); + inputNumberList.add(num4); + inputNumberList.add(num5); + inputNumberList.add(num6); + inputNumberList.add(num7); + inputNumberList.add(num8); + inputNumberList.add(num9); + inputNumberList.add(num10); + inputNumberList.add(num11); + inputNumberList.add(num12); + inputNumberList.add(num13); + inputNumberList.add(num14); + + EmergencyNumber mergeOfNum1AndNum2 = new EmergencyNumber( + "110", + "jp", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, + urn1, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + + EmergencyNumber mergeOfNum3AndNum4 = new EmergencyNumber( + "911", + "us", + "30", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + + List mergedUrns1And2 = new ArrayList<>(); + mergedUrns1And2.add("sos"); + mergedUrns1And2.add("sos:ambulance"); + + EmergencyNumber mergeOfNum5AndNum6 = new EmergencyNumber( + "112", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, + mergedUrns1And2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + + EmergencyNumber mergeOfNum7AndNum8 = new EmergencyNumber( + "108", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber mergeOfNum11AndNum12 = new EmergencyNumber( + "100", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, + urn2, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + + List mergedUrns2And1 = new ArrayList<>(); + mergedUrns2And1.add("sos:ambulance"); + mergedUrns2And1.add("sos"); + + EmergencyNumber mergeOfNum9AndNum10 = new EmergencyNumber( + "102", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, + mergedUrns2And1, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + + EmergencyNumber mergeOfNum13AndNum14 = new EmergencyNumber( + "200", + "in", + "", + EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, + new ArrayList(), + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + + List outputNumberList = new ArrayList<>(); + outputNumberList.add(mergeOfNum1AndNum2); + outputNumberList.add(mergeOfNum3AndNum4); + outputNumberList.add(mergeOfNum5AndNum6); + outputNumberList.add(mergeOfNum7AndNum8); + outputNumberList.add(mergeOfNum9AndNum10); + outputNumberList.add(mergeOfNum11AndNum12); + outputNumberList.add(mergeOfNum13AndNum14); + Collections.sort(outputNumberList); + + EmergencyNumber.mergeSameNumbersInEmergencyNumberList(inputNumberList, true); + assertEquals(outputNumberList, inputNumberList); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java index 0e0bda8dc38ff3de7920ccdd5e2f719f5a97abe1..c47eb3beae906e9c4dc1eaae56b0a1f72bc7e77e 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java @@ -20,27 +20,38 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.IntentFilter; +import android.content.res.AssetManager; +import android.content.res.Resources; import android.os.AsyncResult; import android.os.Environment; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.ServiceState; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.InstrumentationRegistry; -import com.android.internal.telephony.HalVersion; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; +import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyTest; import com.google.i18n.phonenumbers.ShortNumberInfo; @@ -49,6 +60,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + import java.io.BufferedInputStream; import java.io.File; @@ -74,6 +87,7 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { private static final String EMERGENCY_NUMBER_DB_OTA_FILE = "eccdata_ota"; private static final int CONFIG_UNIT_TEST_EMERGENCY_NUMBER_DB_VERSION = 99999; private static final String CONFIG_EMERGENCY_NUMBER_ADDRESS = "54321"; + private static final String CONFIG_EMERGENCY_DUPLICATE_NUMBER = "4321"; private static final String CONFIG_EMERGENCY_NUMBER_COUNTRY = "us"; private static final String CONFIG_EMERGENCY_NUMBER_MNC = ""; private static final String NON_3GPP_EMERGENCY_TEST_NUMBER = "9876543"; @@ -102,7 +116,7 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { private static final int INVALID_SLOT_INDEX_VALID = SubscriptionManager.INVALID_SIM_SLOT_INDEX; private ParcelFileDescriptor mOtaParcelFileDescriptor = null; // Mocked classes - private SubscriptionController mSubControllerMock; + private CarrierConfigManager mCarrierConfigManagerMock; // mEmergencyNumberTrackerMock for mPhone private EmergencyNumberTracker mEmergencyNumberTrackerMock; @@ -115,14 +129,19 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { private File mLocalDownloadDirectory; private ShortNumberInfo mShortNumberInfo; + private Context mMockContext; + private Resources mResources; @Before public void setUp() throws Exception { logd("EmergencyNumberTrackerTest +Setup!"); super.setUp(getClass().getSimpleName()); mShortNumberInfo = mock(ShortNumberInfo.class); - mSubControllerMock = mock(SubscriptionController.class); - mContext = InstrumentationRegistry.getTargetContext(); + mCarrierConfigManagerMock = mock(CarrierConfigManager.class); + + mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()); + mMockContext = mock(Context.class); + mResources = mock(Resources.class); doReturn(mContext).when(mPhone).getContext(); doReturn(0).when(mPhone).getPhoneId(); @@ -141,6 +160,9 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { // Copy an OTA file to the test directory to similate the OTA mechanism simulateOtaEmergencyNumberDb(mPhone); + AssetManager am = new AssetManager.Builder().build(); + doReturn(am).when(mMockContext).getAssets(); + processAllMessages(); logd("EmergencyNumberTrackerTest -Setup!"); } @@ -181,6 +203,14 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); mEmergencyNumberListTestSample.add(emergencyNumberForTest); + + emergencyNumberForTest = new EmergencyNumber( + CONFIG_EMERGENCY_DUPLICATE_NUMBER, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + mEmergencyNumberListTestSample.add(emergencyNumberForTest); } private void sendEmergencyNumberListFromRadio() { @@ -267,6 +297,11 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { } } + private boolean hasDbEmergencyNumbers(List subList, + List list) { + return list.containsAll(subList); + } + private boolean hasDbEmergencyNumber(EmergencyNumber number, List list) { return list.contains(number); } @@ -297,13 +332,6 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { @Test public void testIsSimAbsent() throws Exception { setDsdsPhones(); - replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock); - - // Both sim slots are active - doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_1)); - doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_2)); doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex( eq(SUB_ID_PHONE_1)); doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubscriptionManagerService).getSlotIndex( @@ -311,10 +339,6 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { assertFalse(mEmergencyNumberTrackerMock.isSimAbsent()); // One sim slot is active; the other one is not active - doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_1)); - doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_2)); doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex( eq(SUB_ID_PHONE_1)); doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex( @@ -322,10 +346,6 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { assertFalse(mEmergencyNumberTrackerMock.isSimAbsent()); // Both sim slots are not active - doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_1)); - doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_2)); doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex( anyInt()); assertTrue(mEmergencyNumberTrackerMock.isSimAbsent()); @@ -339,7 +359,42 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { } @Test - public void testUpdateEmergencyCountryIso() throws Exception { + public void testRegistrationForCountryChangeIntent() throws Exception { + EmergencyNumberTracker localEmergencyNumberTracker; + Context spyContext = spy(mContext); + doReturn(spyContext).when(mPhone).getContext(); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(IntentFilter.class); + + localEmergencyNumberTracker = new EmergencyNumberTracker(mPhone, mSimulatedCommands); + verify(spyContext, times(1)).registerReceiver(any(), intentCaptor.capture()); + IntentFilter ifilter = intentCaptor.getValue(); + assertTrue(ifilter.hasAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)); + } + + @Test + public void testUpdateEmergencyCountryIso_whenStatePowerOff() throws Exception { + testUpdateEmergencyCountryIso(ServiceState.STATE_POWER_OFF); + } + + @Test + public void testUpdateEmergencyCountryIso_whenStateInService() throws Exception { + testUpdateEmergencyCountryIso(ServiceState.STATE_IN_SERVICE); + } + + @Test + public void testUpdateEmergencyCountryIso_whenStateOos() throws Exception { + testUpdateEmergencyCountryIso(ServiceState.STATE_OUT_OF_SERVICE); + } + + @Test + public void testUpdateEmergencyCountryIso_whenStateEmergencyOnly() throws Exception { + testUpdateEmergencyCountryIso(ServiceState.STATE_EMERGENCY_ONLY); + } + + private void testUpdateEmergencyCountryIso(int ss) throws Exception { + doReturn(mLocaleTracker).when(mSST).getLocaleTracker(); + doReturn("us").when(mLocaleTracker).getLastKnownCountryIso(); + sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock); mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange("us"); @@ -347,10 +402,14 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("us")); assertTrue(mEmergencyNumberTrackerMock.getLastKnownEmergencyCountryIso().equals("us")); + doReturn(ss).when(mServiceState).getState(); mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange(""); processAllMessages(); assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("")); assertTrue(mEmergencyNumberTrackerMock.getLastKnownEmergencyCountryIso().equals("us")); + + //make sure we look up cached location whenever current iso is null + verify(mLocaleTracker).getLastKnownCountryIso(); } @Test @@ -376,18 +435,9 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { @Test public void testIsEmergencyNumber_FallbackToShortNumberXml_NoSims() throws Exception { - // Set up the Hal version as 1.4 - doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion(); - doReturn(new HalVersion(1, 4)).when(mPhone2).getHalVersion(); - setDsdsPhones(); - replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock); // Both sim slots are not active - doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_1)); - doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_2)); doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex( anyInt()); assertTrue(mEmergencyNumberTrackerMock.isSimAbsent()); @@ -399,7 +449,7 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { processAllMessages(); replaceInstance(ShortNumberInfo.class, "INSTANCE", null, mShortNumberInfo); - mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER, true); + mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER); //verify that we fall back to shortnumber xml when there are no SIMs verify(mShortNumberInfo).isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER, "JP"); @@ -416,30 +466,17 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { } private void testIsEmergencyNumber_NoFallbackToShortNumberXml(int numSims) throws Exception { - // Set up the Hal version as 1.4 - doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion(); - doReturn(new HalVersion(1, 4)).when(mPhone2).getHalVersion(); - assertTrue((numSims > 0 && numSims < 3)); setDsdsPhones(); - replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock); if (numSims == 1) { // One sim slot is active; the other one is not active - doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_1)); - doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_2)); doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex( eq(SUB_ID_PHONE_1)); doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex( eq(SUB_ID_PHONE_2)); } else { //both slots active - doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_1)); - doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubControllerMock).getSlotIndex( - eq(SUB_ID_PHONE_2)); doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex( eq(SUB_ID_PHONE_1)); doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubscriptionManagerService).getSlotIndex( @@ -455,7 +492,7 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { processAllMessages(); replaceInstance(ShortNumberInfo.class, "INSTANCE", null, mShortNumberInfo); - mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER, true); + mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER); //verify we do not use ShortNumber xml verify(mShortNumberInfo, never()).isEmergencyNumber(anyString(), anyString()); @@ -495,38 +532,236 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { } /** - * In 1.3 or less HAL. we should not use database number. + * In 1.4 or above HAL, we should use database number. */ @Test - public void testUsingEmergencyNumberDatabaseWheneverHal_1_3() { - doReturn(new HalVersion(1, 3)).when(mPhone).getHalVersion(); - - sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock); - mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us"); + public void testUsingEmergencyNumberDatabaseWheneverHal_1_4() { + doReturn(mMockContext).when(mPhone).getContext(); + doReturn(mContext.getAssets()).when(mMockContext).getAssets(); + doReturn(mResources).when(mMockContext).getResources(); + doReturn(true).when(mResources).getBoolean( + com.android.internal.R.bool.ignore_emergency_number_routing_from_db); + + EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker( + mPhone, mSimulatedCommands); + emergencyNumberTrackerMock.sendMessage( + emergencyNumberTrackerMock.obtainMessage( + 1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */, + new AsyncResult(null, mEmergencyNumberListTestSample, null))); + sendEmergencyNumberPrefix(emergencyNumberTrackerMock); + emergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us"); processAllMessages(); + /* case 1: check DB number exist or not */ + assertTrue(hasDbEmergencyNumber(CONFIG_EMERGENCY_NUMBER, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + /* case 2: since ignore_emergency_routing_from_db is true. check for all DB numbers with + routing value as unknown by ignoring DB value */ + List completeEmergencyNumberList = new ArrayList<>(); + EmergencyNumber emergencyNumber = new EmergencyNumber( + "888", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + completeEmergencyNumberList.add(emergencyNumber); - boolean hasDatabaseNumber = false; - for (EmergencyNumber number : mEmergencyNumberTrackerMock.getEmergencyNumberList()) { - if (number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) { - hasDatabaseNumber = true; - break; - } - } - assertFalse(hasDatabaseNumber); + emergencyNumber = new EmergencyNumber( + "54321", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + completeEmergencyNumberList.add(emergencyNumber); + + emergencyNumber = new EmergencyNumber( + "654321", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + completeEmergencyNumberList.add(emergencyNumber); + + emergencyNumber = new EmergencyNumber( + "7654321", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + completeEmergencyNumberList.add(emergencyNumber); + + assertTrue(hasDbEmergencyNumbers(completeEmergencyNumberList, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + /* case 3: check the routing type of merged duplicate numbers + between DB number and radio list. */ + EmergencyNumber duplicateEmergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_DUPLICATE_NUMBER, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + assertTrue(hasDbEmergencyNumber(duplicateEmergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); } - /** - * In 1.4 or above HAL, we should use database number. - */ @Test - public void testUsingEmergencyNumberDatabaseWheneverHal_1_4() { - doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion(); + public void testUsingEmergencyNumberDatabaseWithRouting() { + doReturn(mMockContext).when(mPhone).getContext(); + doReturn(mContext.getAssets()).when(mMockContext).getAssets(); + doReturn(mResources).when(mMockContext).getResources(); + doReturn("05").when(mCellIdentity).getMncString(); + doReturn(false).when(mResources).getBoolean( + com.android.internal.R.bool.ignore_emergency_number_routing_from_db); + + EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker( + mPhone, mSimulatedCommands); + emergencyNumberTrackerMock.sendMessage( + emergencyNumberTrackerMock.obtainMessage( + 1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */, + new AsyncResult(null, mEmergencyNumberListTestSample, null))); + sendEmergencyNumberPrefix(emergencyNumberTrackerMock); + emergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us"); + processAllMessages(); - sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock); - mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us"); + // case 1: check DB number with normal routing true and for mnc 05 + EmergencyNumber emergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "05", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + // case 2: check DB number with normal routing true in multiple mnc 05, 45, 47 + emergencyNumber = new EmergencyNumber( + "888", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "05", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + doReturn("47").when(mCellIdentity).getMncString(); + emergencyNumber = new EmergencyNumber( + "888", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "47", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + emergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + /* case 3: check DB number with normal routing false and for mnc 05, + but current cell identity is 04 */ + doReturn("04").when(mCellIdentity).getMncString(); + emergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + // case 4: check DB number with normal routing false + emergencyNumber = new EmergencyNumber( + "654321", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + // case 5: check DB number with normal routing true & empty mnc + emergencyNumber = new EmergencyNumber( + "7654321", CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + /* case 6: check DB number with normal routing true & empty mnc. But same number exist + in radio list. In merge DB routing should be used */ + emergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_DUPLICATE_NUMBER, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE + | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + } + + @Test + public void testUsingEmergencyNumberDatabaseWithRoutingInOOS() { + doReturn(mMockContext).when(mPhone).getContext(); + doReturn(mContext.getAssets()).when(mMockContext).getAssets(); + doReturn(mResources).when(mMockContext).getResources(); + doReturn(false).when(mResources).getBoolean( + com.android.internal.R.bool.ignore_emergency_number_routing_from_db); + + EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker( + mPhone, mSimulatedCommands); + emergencyNumberTrackerMock.sendMessage( + emergencyNumberTrackerMock.obtainMessage( + 1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */, + new AsyncResult(null, mEmergencyNumberListTestSample, null))); + sendEmergencyNumberPrefix(emergencyNumberTrackerMock); + emergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us"); processAllMessages(); - assertTrue(hasDbEmergencyNumber(CONFIG_EMERGENCY_NUMBER, - mEmergencyNumberTrackerMock.getEmergencyNumberList())); + + // Check routing when cellidentity is null, which is oos + doReturn(null).when(mPhone).getCurrentCellIdentity(); + EmergencyNumber emergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + // Check routing when cellidentity is 04, which is not part of normal routing mncs + doReturn(mCellIdentity).when(mPhone).getCurrentCellIdentity(); + doReturn("04").when(mCellIdentity).getMncString(); + emergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); + + // Check routing when cellidentity is 05, which is part of normal routing mncs + doReturn("05").when(mCellIdentity).getMncString(); + emergencyNumber = new EmergencyNumber( + CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY, + "05", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES, + CONFIG_EMERGENCY_NUMBER_SERVICE_URNS, + EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, + EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + assertTrue(hasDbEmergencyNumber(emergencyNumber, + emergencyNumberTrackerMock.getEmergencyNumberList())); } /** @@ -534,9 +769,6 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { */ @Test public void testOtaEmergencyNumberDatabase() { - // Set up the Hal version as 1.4 to apply emergency number database - doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion(); - sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock); mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones(""); processAllMessages(); @@ -597,4 +829,42 @@ public class EmergencyNumberTrackerTest extends TelephonyTest { assertEquals(resultToVerify, resultFromRadio); } + + @Test + public void testOverridingEmergencyNumberPrefixCarrierConfig() throws Exception { + // Capture CarrierConfigChangeListener to emulate the carrier config change notification + doReturn(mMockContext).when(mPhone).getContext(); + doReturn(Context.CARRIER_CONFIG_SERVICE) + .when(mMockContext) + .getSystemService(CarrierConfigManager.class); + doReturn(mCarrierConfigManagerMock) + .when(mMockContext) + .getSystemService(eq(Context.CARRIER_CONFIG_SERVICE)); + ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class); + EmergencyNumberTracker localEmergencyNumberTracker = + new EmergencyNumberTracker(mPhone, mSimulatedCommands); + verify(mCarrierConfigManagerMock) + .registerCarrierConfigChangeListener(any(), listenerArgumentCaptor.capture()); + CarrierConfigManager.CarrierConfigChangeListener carrierConfigChangeListener = + listenerArgumentCaptor.getAllValues().get(0); + + assertFalse(localEmergencyNumberTracker.isEmergencyNumber("*272911")); + + PersistableBundle bundle = new PersistableBundle(); + bundle.putStringArray( + CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY, + new String[] {"*272"}); + doReturn(bundle) + .when(mCarrierConfigManagerMock) + .getConfigForSubId(eq(SUB_ID_PHONE_1), any()); + carrierConfigChangeListener.onCarrierConfigChanged( + mPhone.getPhoneId(), + mPhone.getSubId(), + TelephonyManager.UNKNOWN_CARRIER_ID, + TelephonyManager.UNKNOWN_CARRIER_ID); + processAllMessages(); + + assertTrue(localEmergencyNumberTracker.isEmergencyNumber("*272911")); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2a8e4e2ca33edd1a69966311028dc19e91ec7f47 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java @@ -0,0 +1,1615 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.emergency; + +import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS_PS; +import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME; + +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN; +import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.CarrierConfigManager; +import android.telephony.DisconnectCause; +import android.telephony.EmergencyRegResult; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.Call; +import com.android.internal.telephony.GsmCdmaPhone; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.data.PhoneSwitcher; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +/** + * Unit tests for EmergencyStateTracker + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class EmergencyStateTrackerTest extends TelephonyTest { + private static final String TEST_CALL_ID = "TC@TEST1"; + private static final String TEST_CALL_ID_2 = "TC@TEST2"; + private static final String TEST_SMS_ID = "1111"; + private static final String TEST_SMS_ID_2 = "2222"; + private static final long TEST_ECM_EXIT_TIMEOUT_MS = 500; + private static final EmergencyRegResult E_REG_RESULT = new EmergencyRegResult( + EUTRAN, REGISTRATION_STATE_HOME, DOMAIN_CS_PS, true, true, 0, 1, "001", "01", "US"); + + @Mock EmergencyStateTracker.PhoneFactoryProxy mPhoneFactoryProxy; + @Mock EmergencyStateTracker.PhoneSwitcherProxy mPhoneSwitcherProxy; + @Mock EmergencyStateTracker.TelephonyManagerProxy mTelephonyManagerProxy; + @Mock PhoneSwitcher mPhoneSwitcher; + @Mock RadioOnHelper mRadioOnHelper; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + @SmallTest + public void getInstance_notInitializedTillMake() throws IllegalStateException { + assertThrows(IllegalStateException.class, () -> { + EmergencyStateTracker.getInstance(); + }); + + EmergencyStateTracker.make(mContext, true); + + assertNotNull(EmergencyStateTracker.getInstance()); + } + + @Test + @SmallTest + public void getInstance_returnsSameInstance() { + EmergencyStateTracker.make(mContext, true); + EmergencyStateTracker instance1 = EmergencyStateTracker.getInstance(); + EmergencyStateTracker instance2 = EmergencyStateTracker.getInstance(); + + assertSame(instance1, instance2); + } + + /** + * Test that the EmergencyStateTracker turns on radio, performs a DDS switch and sets emergency + * mode switch when we are not roaming and the carrier only supports SUPL over the data plane. + */ + @Test + @SmallTest + public void startEmergencyCall_radioOff_turnOnRadioSwitchDdsAndSetEmergencyMode() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phones and set radio off + Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */, + false /* isRadioOn */); + setConfigForDdsSwitch(testPhone, null, + CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150"); + // Spy is used to capture consumer in delayDialForDdsSwitch + EmergencyStateTracker spyEst = spy(emergencyStateTracker); + CompletableFuture unused = spyEst.startEmergencyCall(testPhone, TEST_CALL_ID, + false); + + // startEmergencyCall should trigger radio on + ArgumentCaptor callback = ArgumentCaptor + .forClass(RadioOnStateListener.Callback.class); + verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone), + eq(false), eq(0)); + // isOkToCall() should return true once radio is on + assertFalse(callback.getValue() + .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false)); + when(mSST.isRadioOn()).thenReturn(true); + assertTrue(callback.getValue() + .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false)); + // Once radio on is complete, trigger delay dial + callback.getValue().onComplete(null, true); + ArgumentCaptor> completeConsumer = ArgumentCaptor + .forClass(Consumer.class); + verify(spyEst).switchDdsDelayed(eq(testPhone), completeConsumer.capture()); + verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(testPhone.getPhoneId()), + eq(150) /* extensionTime */, any()); + // After dds switch completes successfully, set emergency mode + completeConsumer.getValue().accept(true); + verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any()); + } + + /** + * Test that if startEmergencyCall fails to turn on radio, then it's future completes with + * DisconnectCause.POWER_OFF. + */ + @Test + @SmallTest + public void startEmergencyCall_radioOnFails_returnsDisconnectCausePowerOff() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phones and set radio off + Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */, + false /* isRadioOn */); + + CompletableFuture future = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // startEmergencyCall should trigger radio on + ArgumentCaptor callback = ArgumentCaptor + .forClass(RadioOnStateListener.Callback.class); + verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone), + eq(false), eq(0)); + // Verify future completes with DisconnectCause.POWER_OFF if radio not ready + CompletableFuture unused = future.thenAccept((result) -> { + assertEquals((Integer) result, (Integer) DisconnectCause.POWER_OFF); + }); + callback.getValue().onComplete(null, false /* isRadioReady */); + } + + /** + * Test that the EmergencyStateTracker does not perform a DDS switch when the carrier supports + * control-plane fallback. Radio is set to on so RadioOnHelper not triggered. + */ + @Test + @SmallTest + public void startEmergencyCall_cpFallback_noDdsSwitch() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phones and set radio on + Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */, + true /* isRadioOn */); + setConfigForDdsSwitch(testPhone, null, + CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0"); + + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // Radio already on so shouldn't trigger this + verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(), + anyBoolean(), eq(0)); + // Carrier supports control-plane fallback, so no DDS switch + verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any()); + } + + /** + * Test that the EmergencyStateTracker does not perform a DDS switch if the non-DDS supports + * SUPL. + */ + @Test + @SmallTest + public void startEmergencyCall_supportsSuplOnNonDds_noDdsSwitch() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + false /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */, + true /* isRadioOn */); + setConfigForDdsSwitch(testPhone, null, + CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0"); + + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // non-DDS supports SUPL, so no DDS switch + verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any()); + } + + /** + * Test that the EmergencyStateTracker does not perform a DDS switch when the carrier does not + * support control-plane fallback while roaming. + */ + @Test + @SmallTest + public void startEmergencyCall_roaming_noDdsSwitch() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */, + true /* isRadioOn */); + setConfigForDdsSwitch(testPhone, null, + CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0"); + + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // Is roaming, so no DDS switch + verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any()); + } + + /** + * Test that the EmergencyStateTracker does perform a DDS switch even though the carrier + * supports control-plane fallback and the roaming partner is configured to look like a home + * network. + */ + @Test + @SmallTest + public void startEmergencyCall_roamingCarrierConfig_switchDds() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */, + true /* isRadioOn */); + // Setup voice roaming scenario + String testRoamingOperator = "001001"; + testPhone.getServiceState().setOperatorName("TestTel", "TestTel", testRoamingOperator); + String[] roamingPlmns = new String[] { testRoamingOperator }; + setConfigForDdsSwitch(testPhone, roamingPlmns, + CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0"); + + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // Verify DDS switch + verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */, + eq(0) /* extensionTime */, any()); + } + + /** + * Test that the EmergencyStateTracker does perform a DDS switch even though the carrier + * supports control-plane fallback if we are roaming and the roaming partner is configured to + * use data plane only SUPL. + */ + @Test + @SmallTest + public void startEmergencyCall_roamingCarrierConfigWhileRoaming_switchDds() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */, + true /* isRadioOn */); + // Setup voice roaming scenario + String testRoamingOperator = "001001"; + testPhone.getServiceState().setOperatorName("TestTel", "TestTel", testRoamingOperator); + String[] roamingPlmns = new String[] { testRoamingOperator }; + setConfigForDdsSwitch(testPhone, roamingPlmns, + CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0"); + + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // Verify DDS switch + verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */, + eq(0) /* extensionTime */, any()); + } + + /** + * Test that once EmergencyStateTracker handler receives set emergency mode done message it sets + * IsInEmergencyCall to true, sets LastEmergencyRegResult and completes future with + * DisconnectCause.NOT_DISCONNECTED. + */ + @Test + @SmallTest + public void setEmergencyModeDone_notifiesListenersAndCompletesFuture() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phone + Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */, + true /* isRadioOn */); + setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT); + // Call startEmergencyCall() to set testPhone + CompletableFuture future = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + // Verify future completes with DisconnectCause.NOT_DISCONNECTED + CompletableFuture unused = future.thenAccept((result) -> { + assertEquals((Integer) result, (Integer) DisconnectCause.NOT_DISCONNECTED); + }); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyCall()); + assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT)); + verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + } + + /** + * Test that once EmergencyStateTracker handler receives message to exit emergency mode, it sets + * IsInEmergencyCall to false. + */ + @Test + @SmallTest + public void exitEmergencyModeDone_isInEmergencyCallFalse() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + true /* isSuplDdsSwitchRequiredForEmergencyCall */); + // Create test Phone + Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */, + true /* isRadioOn */); + setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(testPhone); + // Call startEmergencyCall() to set testPhone + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.endCall(TEST_CALL_ID); + processAllMessages(); + + assertFalse(emergencyStateTracker.isInEmergencyCall()); + verify(testPhone).exitEmergencyMode(any(Message.class)); + } + + /** + * Test that onEmergencyCallDomainUpdated updates the domain correctly so ECBM PS domain is + * detected. + */ + @Test + @SmallTest + public void onEmergencyCallDomainUpdated_PsDomain() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + // Call startEmergencyCall() to set testPhone + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // Set call to ACTIVE + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + // set domain + emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_IMS, + TEST_CALL_ID); + // End call to enter ECM + emergencyStateTracker.endCall(TEST_CALL_ID); + + // Make sure CS ECBM is true + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInCdmaEcm()); + assertTrue(emergencyStateTracker.isInImsEcm()); + } + + /** + * Test that onEmergencyCallDomainUpdated updates the domain correctly so ECBM CS domain is + * detected. + */ + @Test + @SmallTest + public void onEmergencyCallDomainUpdated_CsDomain() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + // Call startEmergencyCall() to set testPhone + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // Set call to ACTIVE + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + // set domain + emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA, + TEST_CALL_ID); + // End call to enter ECM + emergencyStateTracker.endCall(TEST_CALL_ID); + + // Make sure IMS ECBM is true + assertTrue(emergencyStateTracker.isInEcm()); + assertTrue(emergencyStateTracker.isInCdmaEcm()); + assertFalse(emergencyStateTracker.isInImsEcm()); + } + + /** + * Ensure that if for some reason we enter ECBM for CS domain and the Phone type is GSM, + * isInCdmaEcm returns false. + */ + @Test + @SmallTest + public void onEmergencyCallDomainUpdated_CsDomain_Gsm() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + // For some reason the Phone is reporting GSM instead of CDMA. + doReturn(PhoneConstants.PHONE_TYPE_GSM).when(testPhone).getPhoneType(); + // Call startEmergencyCall() to set testPhone + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + // Set call to ACTIVE + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + // set domain + emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA, + TEST_CALL_ID); + // End call to enter ECM + emergencyStateTracker.endCall(TEST_CALL_ID); + + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInCdmaEcm()); + assertFalse(emergencyStateTracker.isInImsEcm()); + } + + /** + * Test that onEmergencyTransportChanged sets the new emergency mode. + */ + @Test + @SmallTest + public void onEmergencyTransportChanged_setEmergencyMode() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phones + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true ); + // Call startEmergencyCall() to set testPhone + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + + emergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN); + + verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any()); + } + + /** + * Test that after endCall() is called, EmergencyStateTracker will enter ECM if the call was + * ACTIVE and send related intents. + */ + @Test + @SmallTest + public void endCall_callWasActive_enterEcm() { + // Setup EmergencyStateTracker + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phone + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + // Start emergency call then enter ECM + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + // Set call to ACTIVE + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + // Set ecm as supported + setEcmSupportedConfig(testPhone, true); + + assertFalse(emergencyStateTracker.isInEcm()); + + emergencyStateTracker.endCall(TEST_CALL_ID); + + assertTrue(emergencyStateTracker.isInEcm()); + // Verify intents are sent that ECM is entered + ArgumentCaptor ecmStateIntent = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendStickyBroadcastAsUser(ecmStateIntent.capture(), eq(UserHandle.ALL)); + assertTrue(ecmStateIntent.getValue() + .getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, true)); + // Verify emergency callback mode set on modem + verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any()); + } + + /** + * Test that after endCall() is called, EmergencyStateTracker will not enter ECM if the call was + * not ACTIVE. + */ + @Test + @SmallTest + public void endCall_callNotActive_noEcm() { + // Setup EmergencyStateTracker + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phone + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + // Start emergency call then enter ECM + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + // Call does not reach ACTIVE + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.IDLE, TEST_CALL_ID); + // Set ecm as supported + setEcmSupportedConfig(testPhone, /* ecmSupported= */ true); + + emergencyStateTracker.endCall(TEST_CALL_ID); + + assertFalse(emergencyStateTracker.isInEcm()); + } + + /** + * Test that once endCall() is called and we enter ECM, then we exit ECM after the specified + * timeout. + */ + @Test + @SmallTest + public void endCall_entersEcm_thenExitsEcmAfterTimeout() { + // Setup EmergencyStateTracker + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phone + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(testPhone); + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + // Set call to ACTIVE + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + // Set ecm as supported + setEcmSupportedConfig(testPhone, /* ecmSupported= */ true); + + processAllMessages(); + + emergencyStateTracker.endCall(TEST_CALL_ID); + + assertTrue(emergencyStateTracker.isInEcm()); + + processAllFutureMessages(); + + // Verify exitEmergencyMode() is called after timeout + verify(testPhone).exitEmergencyMode(any(Message.class)); + assertFalse(emergencyStateTracker.isInEmergencyMode()); + } + + /** + * Test that after exitEmergencyCallbackMode() is called, the correct intents are sent and + * emergency mode is exited on the modem. + */ + @Test + @SmallTest + public void exitEmergencyCallbackMode_sendsCorrectIntentsAndExitsEmergencyMode() { + // Setup EmergencyStateTracker + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + // Create test Phone + Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(testPhone); + // Start emergency call then enter ECM + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(testPhone, + TEST_CALL_ID, false); + processAllMessages(); + // Set call to ACTIVE + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + emergencyStateTracker.onEmergencyCallDomainUpdated( + PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID); + // Set ecm as supported + setEcmSupportedConfig(testPhone, /* ecmSupported= */ true); + // End call to enter ECM + emergencyStateTracker.endCall(TEST_CALL_ID); + processAllMessages(); + + // verify ecbm states are correct + assertTrue(emergencyStateTracker.isInEcm()); + assertTrue(emergencyStateTracker.isInImsEcm()); + assertFalse(emergencyStateTracker.isInCdmaEcm()); + + emergencyStateTracker.exitEmergencyCallbackMode(); + processAllFutureMessages(); + + // Ensure ECBM states are all correctly false after we exit. + assertFalse(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInImsEcm()); + assertFalse(emergencyStateTracker.isInCdmaEcm()); + // Intents sent for ECM: one for entering ECM and another for exiting + ArgumentCaptor ecmStateIntent = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(2)) + .sendStickyBroadcastAsUser(ecmStateIntent.capture(), eq(UserHandle.ALL)); + List capturedIntents = ecmStateIntent.getAllValues(); + assertTrue(capturedIntents.get(0) + .getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)); + assertFalse(capturedIntents.get(1) + .getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)); + // Verify exitEmergencyMode() is called only once + verify(testPhone).exitEmergencyMode(any(Message.class)); + } + + @Test + @SmallTest + public void testOnEmergencyTransportChangedUsingDifferentThread() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(() -> { + emergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN); + }); + processAllMessages(); + + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any()); + } + + @Test + @SmallTest + public void testStartEmergencyCallWithTestEmergencyNumber() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + CompletableFuture future = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, true); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + verify(phone0, never()).setEmergencyMode(anyInt(), any(Message.class)); + } + + @Test + @SmallTest + public void testStartEmergencyCallDuringActiveCall() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + // First active call + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Second starting call + CompletableFuture future = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID_2, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencyCallInEcm() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setEcmSupportedConfig(phone0, true); + + // First active call + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + emergencyStateTracker.endCall(TEST_CALL_ID); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEcm()); + + // Second emergency call started. + CompletableFuture future = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID_2, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencyCallUsingDifferenPhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + + // First emergency call + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + // Second emergency call + Phone phone1 = getPhone(1); + CompletableFuture future = emergencyStateTracker.startEmergencyCall(phone1, + TEST_CALL_ID_2, false); + + // Returns DisconnectCause#ERROR_UNSPECIFIED immediately. + assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED), + Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED)); + } + + @Test + @SmallTest + public void testEndCallInEcm() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setEcmSupportedConfig(phone0, true); + + // First active call + CompletableFuture future = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + emergencyStateTracker.endCall(TEST_CALL_ID); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + // Second emergency call started. + future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID_2, false); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN); + emergencyStateTracker.endCall(TEST_CALL_ID_2); + processAllMessages(); + + // At this time, ECM is still running so still in ECM. + assertTrue(emergencyStateTracker.isInEmergencyMode()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WLAN), any(Message.class)); + verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class)); + verify(phone0, never()).exitEmergencyMode(any(Message.class)); + } + + @Test + @SmallTest + public void testStartEmergencySms() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT)); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testEndSms() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT)); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber()); + + verify(phone0).exitEmergencyMode(any(Message.class)); + assertFalse(emergencyStateTracker.isInEmergencyMode()); + } + + @Test + @SmallTest + public void testStartEmergencySmsWithTransportChange() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WLAN); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT)); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WLAN), any(Message.class)); + } + + @Test + @SmallTest + public void testStartEmergencySmsWithTestEmergencyNumber() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false, + /* isRadioOn= */ true); + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, true); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + verify(phone0, never()).setEmergencyMode(anyInt(), any(Message.class)); + } + + @Test + @SmallTest + public void testStartEmergencySmsWhileSmsBeingSent() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + CompletableFuture unused = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID_2, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencySmsWhileEmergencyModeBeingSet() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false, + /* isRadioOn= */ true); + CompletableFuture unused = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID_2, false); + + // Returns DisconnectCause#ERROR_UNSPECIFIED immediately. + assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED), + Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED)); + } + + @Test + @SmallTest + public void testStartEmergencySmsWhileEcmInProgress() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setEcmSupportedConfig(phone0, true); + // Emergency call is ended and the emergency callback is entered. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + emergencyStateTracker.endCall(TEST_CALL_ID); + + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + assertFalse(future.isDone()); + + // Completes the emergency mode setting - MODE_EMERGENCY_CALLBACK + processAllMessages(); + + verify(phone0, times(2)).setEmergencyMode(anyInt(), any(Message.class)); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencySmsUsingDifferentPhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false, + /* isRadioOn= */ true); + CompletableFuture unused = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + + Phone phone1 = getPhone(1); + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone1, + TEST_SMS_ID_2, false); + + // Returns DisconnectCause#ERROR_UNSPECIFIED immediately. + assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED), + Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED)); + } + + @Test + @SmallTest + public void testStartEmergencyCallActiveAndSmsOnSamePhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + // Emergency call is in active. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencyCallInProgressAndSmsOnSamePhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + // Emergency call is in progress. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), msgCaptor.capture()); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + assertFalse(future.isDone()); + + Message msg = msgCaptor.getValue(); + AsyncResult.forMessage(msg, E_REG_RESULT, null); + msg.sendToTarget(); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyCall()); + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencySmsActiveAndCallOnSamePhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(phone0); + // Emergency SMS is in active. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + // Emergency call is being started. + future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID, false); + processAllMessages(); + + verify(phone0).exitEmergencyMode(any(Message.class)); + verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencySmsInProgressAndCallOnSamePhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForExitEmergencyMode(phone0); + // Emergency SMS is in progress. + CompletableFuture smsFuture = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + ArgumentCaptor smsCaptor = ArgumentCaptor.forClass(Message.class); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), smsCaptor.capture()); + + // Emergency call is being started. + CompletableFuture callFuture = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + + assertFalse(smsFuture.isDone()); + assertFalse(callFuture.isDone()); + + // Response message for setEmergencyMode by SMS. + Message msg = smsCaptor.getValue(); + AsyncResult.forMessage(msg, E_REG_RESULT, null); + msg.sendToTarget(); + processAllMessages(); + + // Exit emergency mode and set the emergency mode again by the call when the exit result + // is received for obtaining the latest EmergencyRegResult. + verify(phone0).exitEmergencyMode(any(Message.class)); + ArgumentCaptor callCaptor = ArgumentCaptor.forClass(Message.class); + verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture()); + + // Response message for setEmergencyMode by call. + msg = callCaptor.getAllValues().get(1); + AsyncResult.forMessage(msg, E_REG_RESULT, null); + msg.sendToTarget(); + processAllMessages(); + + // Expect: DisconnectCause#NOT_DISCONNECTED + assertEquals(smsFuture.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(callFuture.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + } + + @Test + @SmallTest + public void testStartEmergencyCallAndSmsOnDifferentPhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + // Emergency call is in active. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Emergency SMS is being started using the different phone. + Phone phone1 = getPhone(1); + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone1, + TEST_SMS_ID, false); + + // Returns DisconnectCause#ERROR_UNSPECIFIED immediately. + assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED), + Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED)); + } + + @Test + @SmallTest + public void testStartEmergencySmsActiveAndCallOnDifferentPhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(phone0); + // Emergency SMS is in active. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + // Emergency call is being started using the different phone. + Phone phone1 = getPhone(1); + setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT); + future = emergencyStateTracker.startEmergencyCall(phone1, TEST_CALL_ID, false); + processAllMessages(); + + verify(phone0).exitEmergencyMode(any(Message.class)); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + } + + @Test + @SmallTest + public void testStartEmergencySmsInProgressAndCallOnDifferentPhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForExitEmergencyMode(phone0); + // Emergency SMS is in progress. + CompletableFuture smsFuture = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + ArgumentCaptor smsCaptor = ArgumentCaptor.forClass(Message.class); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), smsCaptor.capture()); + + // Emergency call is being started using the different phone. + Phone phone1 = getPhone(1); + CompletableFuture callFuture = emergencyStateTracker.startEmergencyCall(phone1, + TEST_CALL_ID, false); + + assertFalse(smsFuture.isDone()); + assertFalse(callFuture.isDone()); + + // Response message for setEmergencyMode by SMS. + Message msg = smsCaptor.getValue(); + AsyncResult.forMessage(msg, E_REG_RESULT, null); + msg.sendToTarget(); + processAllMessages(); + + // Exit emergency mode and set the emergency mode again by the call when the exit result + // is received for obtaining the latest EmergencyRegResult. + verify(phone0).exitEmergencyMode(any(Message.class)); + ArgumentCaptor callCaptor = ArgumentCaptor.forClass(Message.class); + verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture()); + + // Response message for setEmergencyMode by call. + msg = callCaptor.getValue(); + AsyncResult.forMessage(msg, E_REG_RESULT, null); + msg.sendToTarget(); + processAllMessages(); + + // Expect: DisconnectCause#OUTGOING_EMERGENCY_CALL_PLACED + assertEquals(smsFuture.getNow(DisconnectCause.NOT_DISCONNECTED), + Integer.valueOf(DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED)); + // Expect: DisconnectCause#NOT_DISCONNECTED. + assertEquals(callFuture.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + } + + @Test + @SmallTest + public void testExitEmergencyModeCallAndSmsOnSamePhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(phone0); + setEcmSupportedConfig(phone0, false); + // Emergency call is in active. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.endCall(TEST_CALL_ID); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + + emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber()); + processAllMessages(); + + assertFalse(emergencyStateTracker.isInEmergencyMode()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).exitEmergencyMode(any(Message.class)); + } + + @Test + @SmallTest + public void testExitEmergencyModeSmsAndCallOnSamePhone() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(phone0); + setEcmSupportedConfig(phone0, false); + // Emergency call is in active. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber()); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + + emergencyStateTracker.endCall(TEST_CALL_ID); + processAllMessages(); + + assertFalse(emergencyStateTracker.isInEmergencyMode()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).exitEmergencyMode(any(Message.class)); + } + + @Test + @SmallTest + public void testExitEmergencyModeCallAndSmsOnSamePhoneWhenEcmSupported() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(phone0); + setEcmSupportedConfig(phone0, true); + // Emergency call is in active. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.endCall(TEST_CALL_ID); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber()); + processAllMessages(); + + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class)); + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + // ECM timeout. + processAllFutureMessages(); + + assertFalse(emergencyStateTracker.isInEmergencyMode()); + assertFalse(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).exitEmergencyMode(any(Message.class)); + } + + @Test + @SmallTest + public void testExitEmergencyModeCallAndSmsOnSamePhoneWhenEcmSupportedAndModeChanged() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(phone0); + setEcmSupportedConfig(phone0, true); + // Emergency call is in active. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.endCall(TEST_CALL_ID); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + emergencyStateTracker.onEmergencyTransportChanged( + EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN); + emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber()); + processAllMessages(); + + // Enter emergency callback mode and emergency mode changed by SMS end. + verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class)); + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + // ECM timeout. + processAllFutureMessages(); + + assertFalse(emergencyStateTracker.isInEmergencyMode()); + assertFalse(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).exitEmergencyMode(any(Message.class)); + } + + @Test + @SmallTest + public void testExitEmergencyModeSmsAndCallOnSamePhoneWhenEcmSupported() { + EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker( + /* isSuplDdsSwitchRequiredForEmergencyCall= */ true); + Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true, + /* isRadioOn= */ true); + setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT); + setUpAsyncResultForExitEmergencyMode(phone0); + setEcmSupportedConfig(phone0, true); + // Emergency call is in active. + CompletableFuture unused = emergencyStateTracker.startEmergencyCall(phone0, + TEST_CALL_ID, false); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class)); + + emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID); + + // Emergency SMS is being started. + CompletableFuture future = emergencyStateTracker.startEmergencySms(phone0, + TEST_SMS_ID, false); + + // Returns DisconnectCause#NOT_DISCONNECTED immediately. + assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED), + Integer.valueOf(DisconnectCause.NOT_DISCONNECTED)); + + emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber()); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + + emergencyStateTracker.endCall(TEST_CALL_ID); + processAllMessages(); + + assertTrue(emergencyStateTracker.isInEmergencyMode()); + assertTrue(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + + // ECM timeout. + processAllFutureMessages(); + + assertFalse(emergencyStateTracker.isInEmergencyMode()); + assertFalse(emergencyStateTracker.isInEcm()); + assertFalse(emergencyStateTracker.isInEmergencyCall()); + verify(phone0).exitEmergencyMode(any(Message.class)); + } + + private EmergencyStateTracker setupEmergencyStateTracker( + boolean isSuplDdsSwitchRequiredForEmergencyCall) { + doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher(); + doNothing().when(mPhoneSwitcher).overrideDefaultDataForEmergency( + anyInt(), anyInt(), any()); + return new EmergencyStateTracker(mContext, mTestableLooper.getLooper(), + isSuplDdsSwitchRequiredForEmergencyCall, mPhoneFactoryProxy, mPhoneSwitcherProxy, + mTelephonyManagerProxy, mRadioOnHelper, TEST_ECM_EXIT_TIMEOUT_MS); + } + + private Phone setupTestPhoneForEmergencyCall(boolean isRoaming, boolean isRadioOn) { + Phone testPhone0 = makeTestPhone(0 /* phoneId */, ServiceState.STATE_IN_SERVICE, + false /* isEmergencyOnly */); + Phone testPhone1 = makeTestPhone(1 /* phoneId */, ServiceState.STATE_OUT_OF_SERVICE, + false /* isEmergencyOnly */); + List phones = new ArrayList<>(2); + phones.add(testPhone0); + phones.add(testPhone1); + doReturn(isRadioOn).when(testPhone0).isRadioOn(); + doReturn(isRadioOn).when(testPhone1).isRadioOn(); + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); + doReturn(2).when(mTelephonyManagerProxy).getPhoneCount(); + when(mPhoneFactoryProxy.getPhones()).thenReturn(phones.toArray(new Phone[phones.size()])); + testPhone0.getServiceState().setRoaming(isRoaming); + return testPhone0; + } + + private Phone getPhone(int phoneId) { + Phone[] phones = mPhoneFactoryProxy.getPhones(); + return phones[phoneId]; + } + + private Phone makeTestPhone(int phoneId, int serviceState, boolean isEmergencyOnly) { + GsmCdmaPhone phone = mock(GsmCdmaPhone.class); + ServiceState testServiceState = new ServiceState(); + testServiceState.setState(serviceState); + testServiceState.setEmergencyOnly(isEmergencyOnly); + when(phone.getContext()).thenReturn(mContext); + when(phone.getServiceState()).thenReturn(testServiceState); + when(phone.getPhoneId()).thenReturn(phoneId); + when(phone.getSubId()).thenReturn(0); + when(phone.getServiceStateTracker()).thenReturn(mSST); + when(phone.getUnitTestMode()).thenReturn(true); + // Initialize the phone as a CDMA phone for now for ease of testing ECBM. + // Tests can individually override this to GSM if required for the test. + doReturn(PhoneConstants.PHONE_TYPE_CDMA).when(phone).getPhoneType(); + doNothing().when(phone).notifyEmergencyCallRegistrants(anyBoolean()); + return phone; + } + + private void setEcmSupportedConfig(Phone phone, boolean ecmSupported) { + CarrierConfigManager cfgManager = (CarrierConfigManager) mContext + .getSystemService(Context.CARRIER_CONFIG_SERVICE); + cfgManager.getConfigForSubId(phone.getSubId()).putBoolean( + CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL, + ecmSupported); + } + + private void setConfigForDdsSwitch(Phone phone, String[] roaminPlmns, + int suplEmergencyModeType, String esExtensionSec) { + CarrierConfigManager cfgManager = (CarrierConfigManager) mContext + .getSystemService(Context.CARRIER_CONFIG_SERVICE); + cfgManager.getConfigForSubId(phone.getSubId()).putStringArray( + CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, + roaminPlmns); + cfgManager.getConfigForSubId(phone.getSubId()).putInt( + CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, + suplEmergencyModeType); + cfgManager.getConfigForSubId(phone.getSubId()) + .putString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, esExtensionSec); + } + + private void setUpAsyncResultForSetEmergencyMode(Phone phone, EmergencyRegResult regResult) { + doAnswer((invocation) -> { + Object[] args = invocation.getArguments(); + final Message msg = (Message) args[1]; + AsyncResult.forMessage(msg, regResult, null); + msg.sendToTarget(); + return null; + }).when(phone).setEmergencyMode(anyInt(), any(Message.class)); + } + + private void setUpAsyncResultForExitEmergencyMode(Phone phone) { + doAnswer((invocation) -> { + Object[] args = invocation.getArguments(); + final Message msg = (Message) args[0]; + AsyncResult.forMessage(msg, null, null); + msg.sendToTarget(); + return null; + }).when(phone).exitEmergencyMode(any(Message.class)); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2c5a873f5a23c82f15a8b1408872ca13d6245a6f --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.emergency; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.AsyncResult; +import android.os.Handler; +import android.telephony.ServiceState; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneConstants; +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; + +/** + * Tests the RadioOnStateListener, which listens to one Phone and waits until its service state + * changes to accepting emergency calls or in service. If it can not find a tower to camp onto for + * emergency calls, then it will fail after a timeout period. + */ +@RunWith(AndroidJUnit4.class) +public class RadioOnStateListenerTest extends TelephonyTest { + + private static final long TIMEOUT_MS = 1000; + + @Mock Phone mMockPhone; + @Mock RadioOnStateListener.Callback mCallback; + @Mock CommandsInterface mMockCi; + RadioOnStateListener mListener; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + mListener = new RadioOnStateListener(); + doReturn(mSST).when(mMockPhone).getServiceStateTracker(); + mMockPhone.mCi = mMockCi; + } + + @After + public void tearDown() throws Exception { + mListener.setTimeBetweenRetriesMillis(5000); + mListener.setMaxNumRetries(5); + mListener.getHandler().removeCallbacksAndMessages(null); + // Wait for the queue to clear... + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS /* ms timeout */); + mListener = null; + super.tearDown(); + } + + /** + * Ensure that we successfully register for the ServiceState changed messages in Telephony. + */ + @Test + public void testRegisterForCallback() { + mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0); + + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + + verify(mMockPhone).unregisterForServiceStateChanged(any(Handler.class)); + verify(mSatelliteController).unregisterForSatelliteModemStateChanged(anyInt(), any()); + verify(mMockPhone).registerForServiceStateChanged(any(Handler.class), + eq(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED), isNull()); + verify(mSatelliteController, never()).registerForSatelliteModemStateChanged( + anyInt(), any()); + + verify(mMockCi).registerForOffOrNotAvailable(any(Handler.class), + eq(RadioOnStateListener.MSG_RADIO_OFF_OR_NOT_AVAILABLE), isNull()); + } + + /** + * Ensure that we successfully register for the satellite modem state changed messages. + */ + @Test + public void testRegisterForSatelliteCallback() { + doReturn(true).when(mSatelliteController).isSatelliteEnabled(); + mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0); + + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + + verify(mSatelliteController).unregisterForSatelliteModemStateChanged(anyInt(), any()); + verify(mSatelliteController).registerForSatelliteModemStateChanged(anyInt(), any()); + } + + /** + * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns true after + * service state changes, so we are expecting + * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} to + * return true. + */ + @Test + public void testPhoneChangeState_OkToCallTrue() { + ServiceState state = new ServiceState(); + state.setState(ServiceState.STATE_IN_SERVICE); + when(mMockPhone.getServiceState()).thenReturn(state); + when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE); + when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(true); + mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0); + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + + mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED, + new AsyncResult(null, state, null)).sendToTarget(); + + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + verify(mCallback).onComplete(eq(mListener), eq(true)); + } + + /** + * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns true after + * satellite modem state changes, so we are expecting + * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} to + * return true. + */ + @Test + public void testSatelliteChangeState_OkToCallTrue() { + ServiceState state = new ServiceState(); + state.setState(ServiceState.STATE_IN_SERVICE); + when(mMockPhone.getServiceState()).thenReturn(state); + when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE); + when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(true); + mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0); + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + + mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SATELLITE_ENABLED_CHANGED) + .sendToTarget(); + + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + verify(mCallback).onComplete(eq(mListener), eq(true)); + } + + /** + * We never receive a + * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} because + * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns false. + */ + @Test + public void testPhoneChangeState_NoOkToCall_Timeout() { + ServiceState state = new ServiceState(); + state.setState(ServiceState.STATE_OUT_OF_SERVICE); + when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE); + when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false); + when(mMockPhone.getServiceState()).thenReturn(state); + mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0); + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + + mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED, + new AsyncResult(null, state, null)).sendToTarget(); + + waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS); + verify(mCallback, never()).onComplete(any(RadioOnStateListener.class), anyBoolean()); + } + + /** + * Tests {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returning + * false and hitting the max number of retries. This should result in + * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} returning + * false. + */ + @Test + public void testTimeout_RetryFailure() { + ServiceState state = new ServiceState(); + state.setState(ServiceState.STATE_POWER_OFF); + when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE); + when(mMockPhone.getServiceState()).thenReturn(state); + when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false); + mListener.setTimeBetweenRetriesMillis(0/* ms */); + mListener.setMaxNumRetries(2); + + // Wait for the timer to expire and check state manually in onRetryTimeout + mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0); + waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS); + + verify(mCallback).onComplete(eq(mListener), eq(false)); + verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(false), eq(false), eq(false)); + verify(mSatelliteController, never()).requestSatelliteEnabled( + anyInt(), eq(false), eq(false), any()); + } + + @Test + public void testTimeout_RetryFailure_ForEmergency() { + ServiceState state = new ServiceState(); + state.setState(ServiceState.STATE_POWER_OFF); + when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE); + when(mMockPhone.getServiceState()).thenReturn(state); + when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false); + mListener.setTimeBetweenRetriesMillis(0/* ms */); + mListener.setMaxNumRetries(2); + + // Wait for the timer to expire and check state manually in onRetryTimeout + mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 0); + waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS); + + verify(mCallback).onComplete(eq(mListener), eq(false)); + verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(true), eq(true), eq(false)); + verify(mSatelliteController, never()).requestSatelliteEnabled( + anyInt(), eq(false), eq(false), any()); + } + + @Test + public void testTimeout_RetryFailure_WithSatellite() { + doReturn(true).when(mSatelliteController).isSatelliteEnabled(); + ServiceState state = new ServiceState(); + state.setState(ServiceState.STATE_POWER_OFF); + when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE); + when(mMockPhone.getServiceState()).thenReturn(state); + when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false); + mListener.setTimeBetweenRetriesMillis(0/* ms */); + mListener.setMaxNumRetries(2); + + // Wait for the timer to expire and check state manually in onRetryTimeout + mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 0); + waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS); + + verify(mCallback).onComplete(eq(mListener), eq(false)); + verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(true), eq(true), eq(false)); + verify(mSatelliteController, times(2)).requestSatelliteEnabled( + anyInt(), eq(false), eq(false), any()); + } + + @Test + public void testTimeout_OnTimeoutForEmergency() { + ServiceState state = new ServiceState(); + state.setState(ServiceState.STATE_POWER_OFF); + when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE); + when(mMockPhone.getServiceState()).thenReturn(state); + when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())) + .thenReturn(false); + when(mCallback.onTimeout(eq(mMockPhone), anyInt(), anyBoolean())) + .thenReturn(true); + mListener.setTimeBetweenRetriesMillis(0 /* ms */); + mListener.setMaxNumRetries(1); + + // Wait for the timer to expire and check state manually in onRetryTimeout + mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 100); + waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS); + + verify(mCallback).onTimeout(eq(mMockPhone), anyInt(), anyBoolean()); + verify(mCallback).onComplete(eq(mListener), eq(true)); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java index 93c914227eb21987901ebd142d819bf4a5751e43..d4850c814fc3a219f3cdcc06c2a90d753390819c 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java @@ -17,6 +17,7 @@ package com.android.internal.telephony.euicc; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -354,6 +355,29 @@ public class EuiccConnectorTest extends TelephonyTest { assertEquals(mConnector.mAvailableState, mConnector.getCurrentState()); } + @Test + public void testConnectedState_serviceDisconnected() throws Exception { + // Kick off the asynchronous command. + prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */, + true /* hasPriority */); + mConnector = new EuiccConnector(mContext, mLooper.getLooper()); + mConnector.getEid(CARD_ID, new EuiccConnector.GetEidCommandCallback() { + @Override public void onGetEidComplete(String eid) {} + @Override public void onEuiccServiceUnavailable() {} + }); + mLooper.dispatchAll(); + assertEquals(mConnector.mConnectedState, mConnector.getCurrentState()); + // Now, pretend the remote process died. + mConnector.onServiceDisconnected(null /* name */); + mLooper.dispatchAll(); + assertEquals(mConnector.mDisconnectedState, mConnector.getCurrentState()); + // After binder timeout, should now drop back to available state. + mLooper.moveTimeForward(EuiccConnector.BIND_TIMEOUT_MILLIS); + mLooper.dispatchAll(); + assertEquals(mConnector.mAvailableState, mConnector.getCurrentState()); + assertNull(mConnector.getBinder()); + } + private void prepareEuiccApp( boolean hasPermission, boolean requiresBindPermission, boolean hasPriority) { when(mPackageManager.checkPermission( diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java index 85db492203b66334ab80ab90cdc79c22385d3d6b..fc8dfbfcd48f332b05623896c8af86dbc72ed84b 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java @@ -65,6 +65,8 @@ import android.telephony.euicc.EuiccManager; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.euicc.EuiccConnector.GetOtaStatusCommandCallback; import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback; @@ -75,7 +77,6 @@ import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -90,6 +91,7 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; @RunWith(AndroidJUnit4.class) public class EuiccControllerTest extends TelephonyTest { @@ -129,7 +131,7 @@ public class EuiccControllerTest extends TelephonyTest { private static final int SUBSCRIPTION_ID = 12345; private static final String ICC_ID = "54321"; private static final int CARD_ID = 25; - private static final int PORT_INDEX = 0; + private static final int REMOVABLE_CARD_ID = 26; // Mocked classes private EuiccConnector mMockConnector; @@ -320,8 +322,8 @@ public class EuiccControllerTest extends TelephonyTest { SUBSCRIPTION, false /* complete */, null /* result */); verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0 /* detailedCode */); - verify(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), any(), anyBoolean(), - any()); + verify(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), anyInt(), any(), + anyBoolean(), anyBoolean(), any()); } @Test @@ -612,7 +614,6 @@ public class EuiccControllerTest extends TelephonyTest { assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult); } - @Ignore("b/255697307") @Test @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS}) public void testDownloadSubscription_noPrivileges_hasCarrierPrivileges_multiSim() @@ -788,6 +789,70 @@ public class EuiccControllerTest extends TelephonyTest { assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult); } + @Test + public void testGetResolvedPortIndexForSubscriptionSwitchWithOutMEP() throws Exception { + setUpUiccSlotData(); + assertEquals(TelephonyManager.DEFAULT_PORT_INDEX, + mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID)); + } + + @Test + public void testGetResolvedPortIndexForSubscriptionSwitchWithMEP() throws Exception { + setUpUiccSlotDataWithMEP(); + when(mUiccSlot.getPortList()).thenReturn(new int[]{0, 1}); + when(mUiccSlot.isPortActive(TelephonyManager.DEFAULT_PORT_INDEX)).thenReturn(false); + when(mUiccSlot.isPortActive(1)).thenReturn(true); + when(mTelephonyManager.getActiveModemCount()).thenReturn(2); + assertEquals(1, mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID)); + } + + @Test + public void testGetResolvedPortIndexForSubscriptionSwitchWithUiccSlotNull() throws Exception { + assertEquals(TelephonyManager.DEFAULT_PORT_INDEX, + mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID)); + } + + @Test + public void testGetResolvedPortIndexForSubscriptionSwitchWithPsimActiveAndSS() + throws Exception { + when(mUiccController.getUiccSlot(anyInt())).thenReturn(mUiccSlot); + when(mUiccSlot.isRemovable()).thenReturn(true); + when(mUiccSlot.isEuicc()).thenReturn(false); + when(mUiccSlot.isActive()).thenReturn(true); + when(mTelephonyManager.getActiveModemCount()).thenReturn(1); + assertEquals(TelephonyManager.DEFAULT_PORT_INDEX, + mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID)); + } + + @Test + public void testGetResolvedPortIndexForSubscriptionSwitchWithPsimInActiveAndSS() + throws Exception { + setUpUiccSlotDataWithMEP(); + when(mUiccSlot.getPortList()).thenReturn(new int[]{0}); + when(mUiccSlot.isPortActive(TelephonyManager.DEFAULT_PORT_INDEX)).thenReturn(true); + when(mTelephonyManager.getActiveModemCount()).thenReturn(1); + assertEquals(TelephonyManager.DEFAULT_PORT_INDEX, + mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID)); + } + + @Test + public void testgetResolvedPortIndexForDisableSubscriptionForNoActiveSubscription() + throws Exception { + when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(null); + assertEquals(-1, + mController.getResolvedPortIndexForDisableSubscription( + CARD_ID, PACKAGE_NAME, true)); + } + + @Test + public void testgetResolvedPortIndexForDisableSubscriptionForActiveSubscriptions() + throws Exception { + setActiveSubscriptionInfoInMEPMode(); + assertEquals(1, + mController.getResolvedPortIndexForDisableSubscription( + CARD_ID, PACKAGE_NAME, false)); + } + @Test public void testDeleteSubscription_noPrivileges() throws Exception { setHasWriteEmbeddedPermission(false); @@ -889,7 +954,6 @@ public class EuiccControllerTest extends TelephonyTest { anyBoolean(), any(), anyBoolean()); } - @Ignore("b/255697307") @Test @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS}) public void testSwitchToSubscription_emptySubscription_success() throws Exception { @@ -1230,12 +1294,171 @@ public class EuiccControllerTest extends TelephonyTest { SWITCH_WITHOUT_PORT_INDEX_EXCEPTION_ON_DISABLE)); } + @Test + @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK}) + public void testIsSimPortAvailable_invalidCase() { + setUiccCardInfos(false, true, true); + // assert non euicc card id + assertFalse(mController.isSimPortAvailable(REMOVABLE_CARD_ID, 0, TEST_PACKAGE_NAME)); + + // assert invalid port index + assertFalse(mController.isSimPortAvailable(CARD_ID, 5 /* portIndex */, TEST_PACKAGE_NAME)); + } + + @Test + @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK}) + public void testIsSimPortAvailable_port_active() throws Exception { + setUiccCardInfos(false, true, true); + + // port has empty iccid + assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // Set port is active, has valid iccid(may be boot profile) and UiccProfile is empty + setUiccCardInfos(false, true, false); + when(mUiccController.getUiccPortForSlot(anyInt(), anyInt())).thenReturn(mUiccPort); + when(mUiccPort.getUiccProfile()).thenReturn(mUiccProfile); + when(mUiccProfile.isEmptyProfile()).thenReturn(true); + assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // port is active, valid iccid, not empty profile but Phone object is null + when(mUiccPort.getUiccProfile()).thenReturn(mUiccProfile); + when(mUiccProfile.isEmptyProfile()).thenReturn(false); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + // logicalSlotIndex of port#0 is 1, Phone object should be null + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // port is active, valid iccid, not empty profile but no carrier privileges + when(mUiccPort.getUiccProfile()).thenReturn(mUiccProfile); + when(mUiccProfile.isEmptyProfile()).thenReturn(false); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone}); + when(mPhone.getCarrierPrivilegesTracker()).thenReturn(null); + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + when(mPhone.getCarrierPrivilegesTracker()).thenReturn(mCarrierPrivilegesTracker); + when(mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForPackage(TEST_PACKAGE_NAME)) + .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS); + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // port is active, valid iccid, not empty profile and has carrier privileges + when(mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForPackage(TEST_PACKAGE_NAME)) + .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS); + assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + } + + @Test + @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK}) + public void testIsSimPortAvailable_port_inActive() { + setUiccCardInfos(false, false, true); + when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[]{mUiccSlot}); + when(mUiccSlot.isRemovable()).thenReturn(true); + + // Check getRemovableNonEuiccSlot null case + when(mUiccSlot.isEuicc()).thenReturn(true); + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // Check getRemovableNonEuiccSlot isActive() false case + when(mUiccSlot.isEuicc()).thenReturn(false); + when(mUiccSlot.isActive()).thenReturn(false); + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // assert false,multisim is not enabled + when(mUiccSlot.isEuicc()).thenReturn(false); + when(mUiccSlot.isActive()).thenReturn(true); + when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME)) + .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS); + when(mTelephonyManager.isMultiSimEnabled()).thenReturn(false); + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // assert false, caller does not have carrier privileges + setHasWriteEmbeddedPermission(false); + when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true); + when(mUiccSlot.getPortList()).thenReturn(new int[] {0}); + when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt())).thenReturn( + new SubscriptionInfo.Builder().build()); + when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME)) + .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS); + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // assert true, caller does not have carrier privileges but has write_embedded permission + setHasWriteEmbeddedPermission(true); + assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + + // assert true, caller has carrier privileges + setHasWriteEmbeddedPermission(false); + when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME)) + .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS); + assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + } + + @Test + @DisableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK}) + public void testIsSimPortAvailable_port_inActive_disable_compactChange() { + setUiccCardInfos(false, false, true); + when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[]{mUiccSlot}); + when(mUiccSlot.isRemovable()).thenReturn(true); + when(mUiccSlot.isEuicc()).thenReturn(false); + when(mUiccSlot.isActive()).thenReturn(true); + + when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true); + when(mUiccSlot.getPortList()).thenReturn(new int[] {0}); + when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt())).thenReturn( + new SubscriptionInfo.Builder().build()); + when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME)) + .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS); + setHasWriteEmbeddedPermission(true); + + // Even though all conditions are true, isSimPortAvailable should return false as + // compact change is disabled + assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME)); + } + + + private void setUiccCardInfos(boolean isMepSupported, boolean isPortActive, + boolean isEmptyPort) { + List euiccPortInfoList; + if (isMepSupported) { + euiccPortInfoList = Arrays.asList( + new UiccPortInfo(isEmptyPort ? "" : ICC_ID /* iccId */, 0 /* portIdx */, + isPortActive ? 1 : -1 /* logicalSlotIdx */, + isPortActive /* isActive */), + new UiccPortInfo(isEmptyPort ? "" : ICC_ID /* iccId */, 1 /* portIdx */, + -1 /* logicalSlotIdx */, + isPortActive /* isActive */)); + } else { + euiccPortInfoList = Collections.singletonList( + new UiccPortInfo(isEmptyPort ? "" : ICC_ID /* iccId */, 0 /* portIdx */, + isPortActive ? 1 : -1 /* logicalSlotIdx */, + isPortActive /* isActive */) + ); + } + + UiccCardInfo cardInfo1 = new UiccCardInfo(true, CARD_ID, "", 0, + false /* isRemovable */, + isMepSupported /* isMultipleEnabledProfileSupported */, + euiccPortInfoList); + UiccCardInfo cardInfo2 = new UiccCardInfo(false /* isEuicc */, + REMOVABLE_CARD_ID /* cardId */, + "", 0, true /* isRemovable */, + false /* isMultipleEnabledProfileSupported */, + Collections.singletonList( + new UiccPortInfo("" /* iccId */, 0 /* portIdx */, + 0 /* logicalSlotIdx */, true /* isActive */))); + ArrayList cardInfos = new ArrayList<>(); + cardInfos.add(cardInfo1); + cardInfos.add(cardInfo2); + when(mTelephonyManager.getUiccCardsInfo()).thenReturn(cardInfos); + } + private void setUpUiccSlotData() { when(mUiccController.getUiccSlot(anyInt())).thenReturn(mUiccSlot); // TODO(b/199559633): Add test cases for isMultipleEnabledProfileSupported true case when(mUiccSlot.isMultipleEnabledProfileSupported()).thenReturn(false); } + private void setUpUiccSlotDataWithMEP() { + when(mUiccController.getUiccSlot(anyInt())).thenReturn(mUiccSlot); + when(mUiccSlot.isMultipleEnabledProfileSupported()).thenReturn(true); + } + private void setGetEidPermissions( boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) throws Exception { doReturn(hasPhoneStatePrivileged @@ -1263,6 +1486,7 @@ public class EuiccControllerTest extends TelephonyTest { throws Exception { SubscriptionInfo.Builder builder = new SubscriptionInfo.Builder() .setSimSlotIndex(0) + .setPortIndex(mTelephonyManager.DEFAULT_PORT_INDEX) .setDisplayNameSource(SubscriptionManager.NAME_SOURCE_CARRIER_ID) .setEmbedded(true); if (hasPrivileges) { @@ -1300,11 +1524,13 @@ public class EuiccControllerTest extends TelephonyTest { .setNativeAccessRules(hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null) .setEmbedded(true) .setCardId(CARD_ID) + .setPortIndex(mTelephonyManager.DEFAULT_PORT_INDEX) .build(); SubscriptionInfo subInfo2 = new SubscriptionInfo.Builder() .setNativeAccessRules(hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null) .setEmbedded(true) .setCardId(2) + .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX) .build(); when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn( hasPrivileges); @@ -1314,6 +1540,26 @@ public class EuiccControllerTest extends TelephonyTest { when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subInfos); } + private void setActiveSubscriptionInfoInMEPMode() + throws Exception { + SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder() + .setEmbedded(true) + .setCardId(CARD_ID) + .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX) + .build(); + SubscriptionInfo subInfo2 = new SubscriptionInfo.Builder() + .setEmbedded(true) + .setCardId(CARD_ID) + .setPortIndex(1) + .build(); + when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn( + false); + when(mSubscriptionManager.canManageSubscription(subInfo2, PACKAGE_NAME)).thenReturn( + true); + ArrayList subInfos = new ArrayList<>(Arrays.asList(subInfo1, subInfo2)); + when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subInfos); + } + private void prepareOperationSubscription(boolean hasPrivileges) throws Exception { SubscriptionInfo subInfo = new SubscriptionInfo.Builder() .setId(SUBSCRIPTION_ID) @@ -1404,7 +1650,7 @@ public class EuiccControllerTest extends TelephonyTest { @Override public Void answer(InvocationOnMock invocation) throws Exception { EuiccConnector.GetMetadataCommandCallback cb = invocation - .getArgument(3 /* resultCallback */); + .getArgument(5 /* resultCallback */); if (complete) { cb.onGetMetadataComplete(CARD_ID, result); } else { @@ -1412,8 +1658,8 @@ public class EuiccControllerTest extends TelephonyTest { } return null; } - }).when(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), any(), anyBoolean(), - any()); + }).when(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), anyInt(), any(), + anyBoolean(), anyBoolean(), any()); } private void callGetDownloadableSubscriptionMetadata(DownloadableSubscription subscription, diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java index a847a24d057174ff7d07546e62e172204736cca1..c261afe8a6d7c43011160512f4ce126ab29d8be1 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.content.BroadcastReceiver; @@ -55,9 +56,9 @@ import android.test.mock.MockContentResolver; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; +import com.android.ims.ImsManager; import com.android.internal.telephony.FakeSmsContentProvider; import com.android.internal.telephony.InboundSmsHandler; import com.android.internal.telephony.InboundSmsTracker; @@ -192,9 +193,9 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { InboundSmsHandler.SOURCE_NOT_INJECTED); doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory) .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(), - anyInt(), anyBoolean(), - anyBoolean(), nullable(String.class), nullable(String.class), - nullable(String.class), anyBoolean(), anyInt(), anyInt()); + anyInt(), anyBoolean(), + anyBoolean(), nullable(String.class), nullable(String.class), + nullable(String.class), anyBoolean(), anyInt(), anyInt()); createInboundSmsTrackerMultiSim(); @@ -203,12 +204,11 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider); mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(mContext, - mSmsStorageMonitor, mPhone); + mSmsStorageMonitor, mPhone, mTestableLooper.getLooper()); mSmsFilters = new ArrayList<>(); mSmsFilters.add(mSmsFilter); mSmsFilters.add(mSmsFilter2); mGsmInboundSmsHandler.setSmsFiltersForTesting(mSmsFilters); - monitorTestableLooper(new TestableLooper(mGsmInboundSmsHandler.getHandler().getLooper())); doReturn(mGsmInboundSmsHandler).when(mPhone).getInboundSmsHandler(false); doReturn(mCdmaInboundSmsHandler).when(mPhone).getInboundSmsHandler(true); @@ -278,8 +278,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { assertEquals("WaitingState", getCurrentState().getName()); if (allowBgActivityStarts) { Bundle broadcastOptions = mContextFixture.getLastBroadcastOptions(); - assertTrue(broadcastOptions - .getBoolean("android:broadcast.allowBackgroundActivityStarts")); + BroadcastOptions brOptions = new BroadcastOptions(broadcastOptions); + assertTrue(brOptions.allowsBackgroundActivityStarts()); } mContextFixture.sendBroadcastToOrderedBroadcastReceivers(); @@ -310,7 +310,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { processAllMessages(); } - @FlakyTest @Test @MediumTest public void testNewSms() { @@ -330,7 +329,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testNewSmsFromBlockedNumber_noBroadcastsSent() { @@ -348,7 +346,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testNewSmsWithUserLocked_notificationShown() { @@ -375,7 +372,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { any(Notification.class)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testNewSmsFromBlockedNumberWithUserLocked_noNotificationShown() { @@ -405,7 +401,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { any(Notification.class)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testNewSms_filterInvoked_noBroadcastsSent() { @@ -431,7 +426,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { anyBoolean(), anyBoolean(), Mockito.>any()); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testNewSms_filterChaining_noBroadcastsSent() { @@ -483,7 +477,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { assertEquals("IdleState", getCurrentState().getName()); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testClass0Sms() { @@ -515,7 +508,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testBroadcastSms() { @@ -556,7 +548,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(2)); } - @FlakyTest @Test @MediumTest public void testInjectSms() { @@ -617,7 +608,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { InboundSmsHandler.SOURCE_NOT_INJECTED); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testMultiPartSmsWithIncompleteWAP() { @@ -678,12 +668,13 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { assertEquals("IdleState", getCurrentState().getName()); // verify there are three segments in the db and only one of them is not marked as deleted. assertEquals(3, mContentProvider.getNumRows()); - assertEquals(1, mContentProvider.query(sRawUri, null, "deleted=0", null, null).getCount()); + Cursor c = mContentProvider.query(sRawUri, null, "deleted=0", null, null); + assertEquals(1, c.getCount()); verifySmsFiltersInvoked(times(1)); + c.close(); } - @FlakyTest @Test @MediumTest public void testMultiPartSms() { @@ -757,7 +748,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { assertEquals("IdleState", getCurrentState().getName()); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testMultiPartIncompleteSms() { @@ -820,9 +810,9 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { // State machine should go back to idle assertEquals("IdleState", getCurrentState().getName()); verifySmsFiltersInvoked(never()); + c.close(); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testMultiPartSmsWithInvalidSeqNumber() { @@ -882,7 +872,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(never()); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testMultipartSmsFromBlockedNumber_noBroadcastsSent() { @@ -921,7 +910,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testMultipartSmsFromBlockedEmail_noBroadcastsSent() { @@ -977,7 +965,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testMultipartSms_filterInvoked_noBroadcastsSent() { @@ -1027,7 +1014,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { anyBoolean(), anyBoolean(), Mockito.>any()); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testBroadcastUndeliveredUserLocked() throws Exception { @@ -1085,7 +1071,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testBroadcastUndeliveredUserUnlocked() throws Exception { @@ -1123,7 +1108,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testBroadcastUndeliveredDeleted() throws Exception { @@ -1164,7 +1148,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(never()); } - @FlakyTest @Test @MediumTest public void testBroadcastUndeliveredMultiPart() throws Exception { @@ -1180,7 +1163,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { //return InboundSmsTracker objects corresponding to the 2 parts doReturn(mInboundSmsTrackerPart1).doReturn(mInboundSmsTrackerPart2). when(mTelephonyComponentFactory).makeInboundSmsTracker(any(Context.class), - any(Cursor.class), anyBoolean()); + any(Cursor.class), anyBoolean()); SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler); // wait for ScanRawTableThread @@ -1191,7 +1174,6 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifySmsFiltersInvoked(times(1)); } - @FlakyTest // temporarily disabled, see b/182498318 @Test @MediumTest public void testBroadcastUndeliveredMultiSim() throws Exception { @@ -1251,4 +1233,12 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { assertEquals("IdleState", getCurrentState().getName()); } + + @Test + public void testSetImsManager() { + ImsManager imsManager = Mockito.mock(ImsManager.class); + transitionFromStartupToIdle(); + assertTrue(mGsmInboundSmsHandler.setImsManager(imsManager)); + } } + diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java index c0444f1993985ea6c3efbac8c5036a9f01cb5f5f..1c1ca0feff0050e66eca1f2a4fcb9179bc51b889 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java @@ -333,4 +333,3 @@ public class GsmMmiCodeTest extends TelephonyTest { doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt()); } } - diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java index 7da20d8108aa9c7d19b422c5e8886713161f5c14..8374daaac700bbe7732c527b59c52851c08cc567 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java @@ -66,7 +66,6 @@ import com.android.internal.telephony.ContextFixture; import com.android.internal.telephony.ISub; import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsDispatchersController; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.TelephonyTestUtils; import com.android.internal.telephony.TestApplication; @@ -231,7 +230,8 @@ public class GsmSmsDispatcherTest extends TelephonyTest { restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton); restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null); Context realContext = TestApplication.getAppContext(); - realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT)); + realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT), + Context.RECEIVER_EXPORTED); } @Test @@ -376,6 +376,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest { @Test @SmallTest + @Ignore("b/256282780") public void testSendSmsByCarrierApp() throws Exception { mockCarrierApp(); mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK, @@ -383,7 +384,9 @@ public class GsmSmsDispatcherTest extends TelephonyTest { registerTestIntentReceiver(); PendingIntent pendingIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0, - new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE); + new Intent(TEST_INTENT) + .setPackage(TestApplication.getAppContext().getPackageName()), + PendingIntent.FLAG_MUTABLE); mReceivedTestIntent = false; mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms", @@ -438,9 +441,13 @@ public class GsmSmsDispatcherTest extends TelephonyTest { ArrayList sentIntents = new ArrayList<>(); PendingIntent sentIntent1 = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0, - new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE); + new Intent(TEST_INTENT) + .setPackage(TestApplication.getAppContext().getPackageName()), + PendingIntent.FLAG_MUTABLE); PendingIntent sentIntent2 = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0, - new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE); + new Intent(TEST_INTENT) + .setPackage(TestApplication.getAppContext().getPackageName()), + PendingIntent.FLAG_MUTABLE); sentIntents.add(sentIntent1); sentIntents.add(sentIntent2); @@ -450,6 +457,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest { @Test @SmallTest + @Ignore("b/256282780") public void testSendMultipartSmsByCarrierApp() throws Exception { mockCarrierApp(); mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK, @@ -510,7 +518,11 @@ public class GsmSmsDispatcherTest extends TelephonyTest { @Test public void testSendTextWithMessageRef() throws Exception { - int messageRef = mGsmSmsDispatcher.nextMessageRef() + 1; + int messageRef = mGsmSmsDispatcher.nextMessageRef(); + if (mGsmSmsDispatcher.isMessageRefIncrementViaTelephony()) { + messageRef += 1; + } + mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG, null, null, null, null, false, -1, false, -1, false, 0L); @@ -518,7 +530,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest { verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(), any(Message.class)); byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue()); - assertEquals(pdu[1], messageRef); + assertEquals(messageRef, pdu[1]); } @Test @@ -527,7 +539,11 @@ public class GsmSmsDispatcherTest extends TelephonyTest { parts.add("segment1"); parts.add("segment2"); parts.add("segment3"); - int messageRef = mGsmSmsDispatcher.nextMessageRef() + parts.size(); + + int messageRef = mGsmSmsDispatcher.nextMessageRef(); + if (mGsmSmsDispatcher.isMessageRefIncrementViaTelephony()) { + messageRef += parts.size(); + } mGsmSmsDispatcher.sendMultipartText("6501002000" /*destAddr*/, "222" /*scAddr*/, parts, null, null, null, null, false, -1, false, -1, 0L); waitForMs(150); @@ -539,7 +555,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest { verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(), any(Message.class)); byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue()); - assertEquals(pdu[1], messageRef); + assertEquals(messageRef, pdu[1]); } @Test @@ -549,20 +565,16 @@ public class GsmSmsDispatcherTest extends TelephonyTest { doReturn(mIsimUiccRecords).when(mPhone).getIccRecords(); Message msg = mGsmSmsDispatcher.obtainMessage(17); mPhone.getIccRecords().setSmssTpmrValue(-1, msg); - if (isSubscriptionManagerServiceEnabled()) { - mSubscriptionManagerService.setLastUsedTPMessageReference(mPhone.getSubId(), -1); - } else { - SubscriptionController.getInstance().updateMessageRef(mPhone.getSubId(), -1); - } + mSubscriptionManagerService.setLastUsedTPMessageReference(mPhone.getSubId(), -1); mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG, null, null, null, null, false, -1, false, -1, false, 0L); - ArgumentCaptor pduCaptor1 = ArgumentCaptor.forClass(String.class); - verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor1.capture(), + ArgumentCaptor pduCaptor = ArgumentCaptor.forClass(String.class); + verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(), any(Message.class)); - byte[] pdu1 = IccUtils.hexStringToBytes(pduCaptor1.getValue()); - assertEquals(pdu1[1], 0); + byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue()); + assertEquals(0, pdu[1]); } @Test @@ -571,15 +583,14 @@ public class GsmSmsDispatcherTest extends TelephonyTest { mSimulatedCommands); doReturn(mIsimUiccRecords).when(mPhone).getIccRecords(); Message msg = mGsmSmsDispatcher.obtainMessage(17); - - mPhone.getIccRecords().setSmssTpmrValue(255, null); + mPhone.getIccRecords().setSmssTpmrValue(255, msg); mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG, null, null, null, null, false, -1, false, -1, false, 0L); - ArgumentCaptor pduCaptor2 = ArgumentCaptor.forClass(String.class); - verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor2.capture(), + ArgumentCaptor pduCaptor = ArgumentCaptor.forClass(String.class); + verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(), any(Message.class)); - byte[] pdu2 = IccUtils.hexStringToBytes(pduCaptor2.getValue()); - assertEquals(pdu2[1], 0); + byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue()); + assertEquals(0, pdu[1]); } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c6041c8dd67e1bc61fa0c45aab76c4090cc3f59a --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadHandlerTest.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.gsm; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.content.res.Resources; +import android.os.AsyncResult; +import android.os.Message; +import android.telephony.ims.stub.ImsSmsImplBase; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.ims.ImsManager; +import com.android.internal.telephony.CommandException; +import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.GsmCdmaPhone; +import com.android.internal.telephony.InboundSmsHandler; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.uicc.IccIoResult; +import com.android.internal.telephony.uicc.IccUtils; +import com.android.internal.util.HexDump; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class UsimDataDownloadHandlerTest extends TelephonyTest { + // Mocked classes + private CommandsInterface mMockCi; + protected GsmCdmaPhone mMockPhone; + private ImsManager mMockImsManager; + private Resources mMockResources; + + private UsimDataDownloadHandler mUsimDataDownloadHandler; + private Phone mPhoneObj; + private Phone[] mPhoneslist; + private int mPhoneId; + private int mSlotId = 0; + private int mToken = 0; + private int mSmsSource = 1; + private byte[] mTpdu; + private byte[] mSmsAckPdu; + //Envelope is created as per TS.131.111 for SMS-PP data downwnload operation + private String mEnvelope = "D11502028281060591896745F306068199201269231300"; + //SMS TPDU for Class2 according to TS.123.040 + private String mPdu = "07914151551512f221110A81785634121000000666B2996C2603"; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + mMockCi = Mockito.mock(CommandsInterface.class); + mMockPhone = Mockito.mock(GsmCdmaPhone.class); + mMockImsManager = Mockito.mock(ImsManager.class); + mMockResources = Mockito.mock(Resources.class); + + mPhoneslist = new Phone[] {mMockPhone}; + mTpdu = HexDump.hexStringToByteArray(mPdu); + + //Use reflection to mock + replaceInstance(PhoneFactory.class, "sPhones", null, mPhoneslist); + replaceInstance(PhoneFactory.class, "sPhone", null, mMockPhone); + + mPhoneObj = PhoneFactory.getPhone(mSlotId); + assertNotNull(mPhoneObj); + mPhoneId = mPhoneObj.getPhoneId(); + doReturn(mSmsStats).when(mPhoneObj).getSmsStats(); + + //new UsimDataDownloadHandlerThread(TAG).start(); + //waitUntilReady(); + mUsimDataDownloadHandler = new UsimDataDownloadHandler(mMockCi, mPhoneId); + mUsimDataDownloadHandler.setImsManager(mMockImsManager); + } + + @After + public void tearDown() throws Exception { + mUsimDataDownloadHandler = null; + mPhoneslist = null; + super.tearDown(); + } + + @Test + public void sendEnvelopeForException() throws Exception { + setSmsPPSimConfig(false); + com.android.internal.telephony.gsm.SmsMessage sms = + com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(1); + AsyncResult.forMessage(response, null, new CommandException( + CommandException.Error.OPERATION_NOT_ALLOWED)); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .sendEnvelopeWithStatus(anyString(), any(Message.class)); + + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + verify(mMockCi).acknowledgeLastIncomingGsmSms(false, + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, null); + + setSmsPPSimConfig(true); + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], + ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC); + } + + @Test + public void sendEnvelopeDataDownloadSuccess() throws Exception { + setSmsPPSimConfig(true); + com.android.internal.telephony.gsm.SmsMessage sms = + com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu); + mSmsAckPdu = createSmsAckPdu(true, mEnvelope, sms); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(1); + IccIoResult iir = new IccIoResult(0x90, 0x00, + IccUtils.hexStringToBytes(mEnvelope)); + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .sendEnvelopeWithStatus(anyString(), any(Message.class)); + + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], + ImsSmsImplBase.DELIVER_STATUS_OK, mSmsAckPdu); + + setSmsPPSimConfig(false); + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + verify(mMockCi).acknowledgeIncomingGsmSmsWithPdu(true, + IccUtils.bytesToHexString(mSmsAckPdu), null); + } + + @Test + public void sendEnvelopeDataDownloadFailed() throws Exception { + setSmsPPSimConfig(false); + com.android.internal.telephony.gsm.SmsMessage sms = + com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(1); + IccIoResult iir = new IccIoResult(0x93, 0x00, + IccUtils.hexStringToBytes(mEnvelope)); + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .sendEnvelopeWithStatus(anyString(), any(Message.class)); + + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + verify(mMockCi).acknowledgeLastIncomingGsmSms(false, + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY, null); + + setSmsPPSimConfig(true); + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], + ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC); + } + + @Test + public void sendEnvelopeForSw1_62() throws Exception { + setSmsPPSimConfig(false); + com.android.internal.telephony.gsm.SmsMessage sms = + com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu); + mSmsAckPdu = createSmsAckPdu(false, mEnvelope, sms); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(1); + IccIoResult iir = new IccIoResult(0x62, 0x63, + IccUtils.hexStringToBytes(mEnvelope)); + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .sendEnvelopeWithStatus(anyString(), any(Message.class)); + + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + verify(mMockCi).acknowledgeIncomingGsmSmsWithPdu(false, + IccUtils.bytesToHexString(mSmsAckPdu), null); + + setSmsPPSimConfig(true); + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], + ImsSmsImplBase.DELIVER_STATUS_OK, mSmsAckPdu); + } + + @Test + public void smsCompleteForException() throws Exception { + setSmsPPSimConfig(false); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(3); + AsyncResult.forMessage(response, null, new CommandException( + CommandException.Error.OPERATION_NOT_ALLOWED)); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .writeSmsToSim(anyInt(), anyString(), anyString(), any(Message.class)); + + int[] responseInfo = {mSmsSource, mTpdu[9], mToken}; + Message msg = mUsimDataDownloadHandler.obtainMessage(3 /* EVENT_WRITE_SMS_COMPLETE */, + responseInfo); + AsyncResult.forMessage(msg, null, new CommandException( + CommandException.Error.OPERATION_NOT_ALLOWED)); + mUsimDataDownloadHandler.handleMessage(msg); + verify(mMockCi).acknowledgeLastIncomingGsmSms(false, + CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null); + + setSmsPPSimConfig(true); + mUsimDataDownloadHandler.handleMessage(msg); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], + ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC); + } + + @Test + public void smsComplete() throws Exception { + setSmsPPSimConfig(true); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(3); + IccIoResult iir = new IccIoResult(0x90, 0x00, + IccUtils.hexStringToBytes(mEnvelope)); + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .writeSmsToSim(anyInt(), anyString(), anyString(), any(Message.class)); + + int[] responseInfo = {mSmsSource, mTpdu[9], mToken}; + Message msg = mUsimDataDownloadHandler.obtainMessage(3 /* EVENT_WRITE_SMS_COMPLETE */, + responseInfo); + AsyncResult.forMessage(msg, null, null); + mUsimDataDownloadHandler.handleMessage(msg); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], ImsSmsImplBase.DELIVER_STATUS_OK); + + setSmsPPSimConfig(false); + mUsimDataDownloadHandler.handleMessage(msg); + verify(mMockCi).acknowledgeLastIncomingGsmSms(true, 0, null); + } + + @Test + public void failureEnvelopeResponse() throws Exception { + setSmsPPSimConfig(false); + com.android.internal.telephony.gsm.SmsMessage sms = + com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(1); + IccIoResult iir = new IccIoResult(0x62, 0x63, + IccUtils.hexStringToBytes(null)); //ForNullResponseBytes + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .sendEnvelopeWithStatus(anyString(), any(Message.class)); + + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + verify(mMockCi).acknowledgeLastIncomingGsmSms(false, + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, null); + + setSmsPPSimConfig(true); + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], + ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC); + } + + @Test + public void successEnvelopeResponse() throws Exception { + setSmsPPSimConfig(false); + com.android.internal.telephony.gsm.SmsMessage sms = + com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu); + + doAnswer( + invocation -> { + Message response = invocation.getArgument(1); + IccIoResult iir = new IccIoResult(0x90, 0x00, + IccUtils.hexStringToBytes(null)); //ForNullResponseBytes + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }) + .when(mMockCi) + .sendEnvelopeWithStatus(anyString(), any(Message.class)); + + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + verify(mMockCi).acknowledgeLastIncomingGsmSms(true, 0, null); + + setSmsPPSimConfig(true); + mUsimDataDownloadHandler.startDataDownload(sms, + InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken); + processAllMessages(); + //mTpdu[9] holds messageReference value + verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], ImsSmsImplBase.DELIVER_STATUS_OK); + } + + //To set "config_smppsim_response_via_ims" for testing purpose + private void setSmsPPSimConfig(boolean config) { + mUsimDataDownloadHandler.setResourcesForTest(mMockResources); + doReturn(config).when(mMockResources).getBoolean( + com.android.internal.R.bool.config_smppsim_response_via_ims); + } + + private byte[] createSmsAckPdu(boolean success, String envelope, SmsMessage smsMessage) { + byte[] responseBytes = IccUtils.hexStringToBytes(envelope); + int dcs = 0x00; + int pid = smsMessage.getProtocolIdentifier(); + byte[] smsAckPdu; + int index = 0; + if (success) { + smsAckPdu = new byte[responseBytes.length + 5]; + smsAckPdu[index++] = 0x00; + smsAckPdu[index++] = 0x07; + } else { + smsAckPdu = new byte[responseBytes.length + 6]; + smsAckPdu[index++] = 0x00; + smsAckPdu[index++] = (byte) + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR; + smsAckPdu[index++] = 0x07; + } + + smsAckPdu[index++] = (byte) pid; + smsAckPdu[index++] = (byte) dcs; + + if (((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0)) { + int septetCount = responseBytes.length * 8 / 7; + smsAckPdu[index++] = (byte) septetCount; + } else { + smsAckPdu[index++] = (byte) responseBytes.length; + } + + System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length); + return smsAckPdu; + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c6f45d9387b63f8318351c76d9b36ec9c9bd4aff --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java @@ -0,0 +1,812 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.ims; + +import static com.android.internal.telephony.ims.ImsEnablementTracker.COMMAND_DISABLE_MSG; +import static com.android.internal.telephony.ims.ImsEnablementTracker.COMMAND_ENABLE_MSG; +import static com.android.internal.telephony.ims.ImsEnablementTracker.COMMAND_RESETTING_DONE; + +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.telephony.ims.aidl.IImsServiceController; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +/** + * Unit tests for ImsEnablementTracker + */ +@RunWith(AndroidJUnit4.class) +public class ImsEnablementTrackerTest extends ImsTestBase { + + // default timeout values(millisecond) for handler working + private static final int DEFAULT_TIMEOUT = 100; + + // default delay values(millisecond) for handler working + private static final int DEFAULT_DELAY = 150; + + private static final int SLOT_1 = 0; + private static final int SUB_1 = 11; + + private static final int SLOT_2 = 1; + private static final int SUB_2 = 22; + private static final long TEST_REQUEST_THROTTLE_TIME_MS = 3000L; + + // Mocked classes + @Mock + IImsServiceController mMockServiceControllerBinder; + + private TestableImsEnablementTracker mTracker; + private Handler mHandler; + + private static long sLastImsOperationTimeMs = 0L; + + private static class TestableImsEnablementTracker extends ImsEnablementTracker { + private ImsEnablementTrackerTest mOwner; + + TestableImsEnablementTracker(Looper looper, IImsServiceController controller, int state, + int numSlots, ImsEnablementTrackerTest owner) { + super(looper, controller, state, numSlots); + mOwner = owner; + } + + @Override + protected long getLastOperationTimeMillis() { + return ImsEnablementTrackerTest.sLastImsOperationTimeMs; + } + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @After + @Override + public void tearDown() throws Exception { + // Make sure the handler is empty before finishing the test. + waitForHandlerAction(mHandler, TEST_REQUEST_THROTTLE_TIME_MS); + + mTracker = null; + super.tearDown(); + } + + @SmallTest + @Test + public void testEnableCommandInDefaultState() throws RemoteException { + // Verify that when the enable command is received in the Default state and enableIms + // is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testDisableCommandInDefaultState() throws RemoteException { + // Verify that when the disable command is received in the Default state and disableIms + // is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testResetCommandInDefaultState() throws RemoteException { + // Verify that when reset command is received in the Default state, it should be ignored. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.resetIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1)); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testEnableCommandInEnabledState() throws RemoteException { + // Verify that received the enable command is not handle in the Enabled state, + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testDisableCommandInEnabledState() throws RemoteException { + // Verify that when the disable command is received in the Enabled state and disableIms + // is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testResetCommandInEnabledState() throws RemoteException { + // Verify that when the reset command is received in the Enabled state and disableIms + // and enableIms are called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.resetIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1)); + + waitForHandlerActionDelayed(mHandler, TEST_REQUEST_THROTTLE_TIME_MS, + TEST_REQUEST_THROTTLE_TIME_MS + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testDisableCommandInDisabledState() throws RemoteException { + // Verify that when disable command is received in the Disabled state, it should be ignored. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verifyZeroInteractions(mMockServiceControllerBinder); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testEnableCommandInDisabledState() throws RemoteException { + // Verify that when the enable command is received in the Disabled state and enableIms + // is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testEnableCommandWithoutTimeoutInDisableState() throws RemoteException { + // Verify that when the enable command is received in the Disabled state. After throttle + // time expired, the enableIms is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testResetCommandInDisabledState() throws RemoteException { + // Verify that the reset command is received in the Disabled state and it`s not handled. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1, 0); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.resetIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1)); + + // The disableIms was called. So set the last operation time to current. + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testEnableCommandInDisablingState() throws RemoteException { + // Verify that when enable command is received in the Disabling state, it should be ignored. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verifyZeroInteractions(mMockServiceControllerBinder); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testDisablingMessageInDisablingState() throws RemoteException { + // Verify that when the internal disable message is received in the Disabling state and + // disableIms is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).disableIms(anyInt(), anyInt()); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testResetCommandInDisablingState() throws RemoteException { + // Verify when the reset command is received in the Disabling state the disableIms and + // enableIms are called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.resetIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1)); + + // The disableIms was called. So set the last operation time to current. + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testEnablingMessageInEnablingState() throws RemoteException { + // Verify that when the internal enable message is received in the Enabling state and + // enableIms is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).enableIms(anyInt(), anyInt()); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testDisableCommandInEnablingState() throws RemoteException { + // Verify that when the disable command is received in the Enabling state and + // clear pending message and disableIms is not called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testResetCommandWithEnablingState() throws RemoteException { + // Verify that when reset command is received in the Enabling state, it should be ignored. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.resetIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1)); + + // The disableIms was called. So set the last operation time to current. + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testEnableCommandInResettingState() throws RemoteException { + // Verify that when the enable command is received in the Resetting state and + // enableIms is not called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verifyZeroInteractions(mMockServiceControllerBinder); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_RESETTING)); + } + + @SmallTest + @Test + public void testDisableCommandInResettingState() throws RemoteException { + // Verify that when the disable command is received in the Resetting state and + // disableIms is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verifyZeroInteractions(mMockServiceControllerBinder); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_RESETTING)); + } + + @SmallTest + @Test + public void testResettingMessageInResettingState() throws RemoteException { + // Verify that when the internal reset message is received in the Resetting state and + // disableIms and enableIms are called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + + // Wait for the throttle time. + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).resetIms(anyInt(), anyInt()); + + // Set the last operation time to current to verify the message with delay. + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), anyInt()); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt()); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testDisableEnableMessageInResettingState() throws RemoteException { + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // Simulation. + // In Resetting state, disableIms() called before doing resetIms(). + // After doing resetIms(), during the throttle time(before doing disableIms()), + // enableIms() called. Finally skip disableIms() and do enableIms(). + mHandler.removeMessages(COMMAND_RESETTING_DONE); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1)); + + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt()); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testEnableDisableMessageInResettingState() throws RemoteException { + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // Simulation. + // In Resetting state, enableIms() called before doing resetIms(). + // After doing resetIms(), during the throttle time(before doing enableIms()), + // disableIms() called. Finally skip enableIms() and do disableIms(). + mHandler.removeMessages(COMMAND_RESETTING_DONE); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1)); + + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testRepeatEnableMessageInResettingState() throws RemoteException { + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // Simulation. + // In Resetting state, enableIms(), disableIms() are called repeatedly. + // After doing resetIms(), latest enableIms() should perform. + mHandler.removeMessages(COMMAND_RESETTING_DONE); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1)); + + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testRepeatDisableMessageInResettingState() throws RemoteException { + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // Simulation. + // In Resetting state, enableIms(), disableIms() are called repeatedly. + // After doing resetIms(), latest disableIms() should perform. + mHandler.removeMessages(COMMAND_RESETTING_DONE); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1)); + mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1)); + + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testConsecutiveCommandInEnabledState() throws RemoteException { + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + + // Set the last operation time to current to verify the message with delay. + sLastImsOperationTimeMs = System.currentTimeMillis(); + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLING)); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLING)); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).resetIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testConsecutiveCommandInDisabledState() throws RemoteException { + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // Set the last operation time to current to verify the message with delay. + sLastImsOperationTimeMs = System.currentTimeMillis(); + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLING)); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + + mTracker.resetIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_RESETTING)); + + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testSubIdChangeToInvalidAndEnableCommand() throws RemoteException { + // Verify that when the enable command is received in the Default state and enableIms + // is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + mTracker.subIdChangedToInvalid(SLOT_1); + waitForHandler(mHandler); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DEFAULT)); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testEnableCommandWithDifferentSlotId() throws RemoteException { + // Verify that when the enable command is received in the Default state and enableIms + // is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 2, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + Handler handlerForSlot2 = mTracker.getHandler(SLOT_2); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + waitForHandler(handlerForSlot2); + + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandler(mHandler); + + verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + assertTrue(mTracker.isState(SLOT_2, mTracker.STATE_IMS_DEFAULT)); + + mTracker.enableIms(SLOT_2, SUB_2); + waitForHandler(handlerForSlot2); + + verify(mMockServiceControllerBinder).enableIms(eq(SLOT_2), eq(SUB_2)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + assertTrue(mTracker.isState(SLOT_2, mTracker.STATE_IMS_ENABLED)); + + mTracker.setNumOfSlots(1); + sLastImsOperationTimeMs = System.currentTimeMillis(); + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder).disableIms(eq(SLOT_1), eq(SUB_1)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + + mTracker.setNumOfSlots(2); + sLastImsOperationTimeMs = System.currentTimeMillis(); + mTracker.disableIms(SLOT_2, SUB_2); + waitForHandler(handlerForSlot2); + + verify(mMockServiceControllerBinder).disableIms(eq(SLOT_2), eq(SUB_2)); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + assertTrue(mTracker.isState(SLOT_2, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testEnableCommandInPostResettingState() throws RemoteException { + // Verify that when the enable/disable commands are received in the PostResetting state + // and final enableIms is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_POSTRESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // to confirm the slotId, subId for COMMAND_POST_RESETTING_DONE + mHandler.removeMessages(mTracker.COMMAND_POST_RESETTING_DONE); + mHandler.sendMessageDelayed(mHandler.obtainMessage(mTracker.COMMAND_POST_RESETTING_DONE, + SLOT_1, SUB_1), mTracker.getRemainThrottleTime()); + + mTracker.enableIms(SLOT_1, SUB_1); + mTracker.disableIms(SLOT_1, SUB_1); + mTracker.enableIms(SLOT_1, SUB_1); + mTracker.disableIms(SLOT_1, SUB_1); + mTracker.enableIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt()); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + @SmallTest + @Test + public void testDisableCommandInPostResettingState() throws RemoteException { + // Verify that when the enable/disable commands are received in the PostResetting state + // and final disableIms is called. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_POSTRESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // to confirm the slotId, subId for COMMAND_POST_RESETTING_DONE + mHandler.removeMessages(mTracker.COMMAND_POST_RESETTING_DONE); + mHandler.sendMessageDelayed(mHandler.obtainMessage(mTracker.COMMAND_POST_RESETTING_DONE, + SLOT_1, SUB_1), mTracker.getRemainThrottleTime()); + + mTracker.disableIms(SLOT_1, SUB_1); + mTracker.enableIms(SLOT_1, SUB_1); + mTracker.disableIms(SLOT_1, SUB_1); + mTracker.enableIms(SLOT_1, SUB_1); + mTracker.disableIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), anyInt()); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED)); + } + + @SmallTest + @Test + public void testResetCommandInPostResettingState() throws RemoteException { + // Verify that when the enable/disable/reset commands are received in the PostResetting + // state and final enableIms is called without calling resetIms again. + mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_POSTRESETTING, 1, + System.currentTimeMillis()); + mHandler = mTracker.getHandler(SLOT_1); + // Wait for a while for the state machine to be ready. + waitForHandler(mHandler); + + // to confirm the slotId, subId for COMMAND_POST_RESETTING_DONE + mHandler.removeMessages(mTracker.COMMAND_POST_RESETTING_DONE); + mHandler.sendMessageDelayed(mHandler.obtainMessage(mTracker.COMMAND_POST_RESETTING_DONE, + SLOT_1, SUB_1), mTracker.getRemainThrottleTime()); + + mTracker.disableIms(SLOT_1, SUB_1); + mTracker.enableIms(SLOT_1, SUB_1); + mTracker.resetIms(SLOT_1, SUB_1); + waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(), + mTracker.getRemainThrottleTime() + DEFAULT_DELAY); + + verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1)); + verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt()); + verify(mMockServiceControllerBinder, never()).resetIms(eq(SLOT_1), anyInt()); + assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED)); + } + + private TestableImsEnablementTracker createTracker(IImsServiceController binder, int state, + int numSlots, long initLastOperationTime) { + sLastImsOperationTimeMs = initLastOperationTime; + TestableImsEnablementTracker tracker = new TestableImsEnablementTracker( + Looper.getMainLooper(), binder, state, numSlots, this); + return tracker; + } + + private void waitForHandler(Handler h) { + waitForHandlerActionDelayed(h, DEFAULT_TIMEOUT, DEFAULT_DELAY); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java index 02484ce17bd53228a8ab995f35179d0cce55ef6e..abc231a94fd2476cb5ea5dbab8520d425037a809 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java @@ -956,7 +956,7 @@ public class ImsResolverTest extends ImsTestBase { carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, ImsFeature.FEATURE_RCS)); // Assume that there is a CarrierConfig change that kicks off query to carrier service. sendCarrierConfigChanged(1, 1); - setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2); + setupDynamicQueryFeaturesMultiSim(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2); verify(carrierController).changeImsServiceFeatures(eq(carrierFeatures), any(SparseIntArray.class)); deviceFeatureSet = convertToHashSet(deviceFeatures, 0); @@ -2021,6 +2021,7 @@ public class ImsResolverTest extends ImsTestBase { */ private void startBindCarrierConfigAlreadySet() { mTestImsResolver.initialize(); + processAllMessages(); ArgumentCaptor receiversCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any()); @@ -2042,6 +2043,7 @@ public class ImsResolverTest extends ImsTestBase { */ private void startBindNoCarrierConfig(int numSlots) { mTestImsResolver.initialize(); + processAllMessages(); ArgumentCaptor receiversCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any()); @@ -2068,6 +2070,15 @@ public class ImsResolverTest extends ImsTestBase { processAllMessages(); } + private void setupDynamicQueryFeaturesMultiSim(ComponentName name, + HashSet features, int times) { + processAllFutureMessages(); + // ensure that startQuery was called + verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class)); + mDynamicQueryListener.onComplete(name, features); + processAllMessages(); + } + private void setupDynamicQueryFeaturesFailure(ComponentName name, int times) { processAllMessages(); // ensure that startQuery was called diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java index b80c6b066eb5bb8e0550c25bf5e89c23afba256c..4a3ceaad16588dc076c28e5c8a1d89a2ee8dcd45 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java @@ -140,6 +140,8 @@ public class ImsServiceControllerCompatTest extends ImsTestBase { SparseIntArray slotIdToSubIdMap = new SparseIntArray(); slotIdToSubIdMap.put(SLOT_0, SUB_1); ServiceConnection conn = bindAndConnectService(slotIdToSubIdMap); + long delay = mTestImsServiceController.getRebindDelay(); + waitForHandlerActionDelayed(mHandler, delay, 2 * delay); // add the MMTelFeature verify(mMockServiceControllerBinder).createMMTelFeature(SLOT_0); verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0), @@ -149,6 +151,8 @@ public class ImsServiceControllerCompatTest extends ImsTestBase { validateMmTelFeatureContainerExists(SLOT_0); // Remove the feature conn.onBindingDied(mTestComponentName); + delay = REBIND_RETRY.getStartDelay(); + waitForHandlerActionDelayed(mHandler, delay, 2 * delay); verify(mMmTelCompatAdapterSpy).onFeatureRemoved(); verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL)); diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java index 113829f88b004b5461c665bd507654ace24c37de..adfc4a3833eca2878fa7db1477e6b0c8ae0be39c 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java @@ -634,6 +634,8 @@ public class ImsServiceControllerTest extends ImsTestBase { conn.onServiceDisconnected(mTestComponentName); + long delay = mTestImsServiceController.getRebindDelay(); + waitForHandlerActionDelayed(mHandler, delay, 2 * delay); verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController)); verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS), @@ -660,6 +662,8 @@ public class ImsServiceControllerTest extends ImsTestBase { conn.onServiceDisconnected(mTestComponentName); + long delay = mTestImsServiceController.getRebindDelay(); + waitForHandlerActionDelayed(mHandler, delay, 2 * delay); verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController)); verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS), @@ -762,6 +766,8 @@ public class ImsServiceControllerTest extends ImsTestBase { conn.onBindingDied(null /*null*/); + long delay = REBIND_RETRY.getStartDelay(); + waitForHandlerActionDelayed(mHandler, delay, 2 * delay); verify(mMockContext).unbindService(eq(conn)); verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController)); @@ -787,6 +793,8 @@ public class ImsServiceControllerTest extends ImsTestBase { slotIdToSubIdMap.put(SLOT_0, SUB_2); bindAndNullServiceError(testFeatures, slotIdToSubIdMap.clone()); + long delay = mTestImsServiceController.getRebindDelay(); + waitForHandlerActionDelayed(mHandler, delay, 2 * delay); verify(mMockCallbacks, never()).imsServiceFeatureCreated(anyInt(), anyInt(), eq(mTestImsServiceController)); verify(mMockCallbacks).imsServiceBindPermanentError(eq(mTestComponentName)); @@ -1304,7 +1312,7 @@ public class ImsServiceControllerTest extends ImsTestBase { conn.onBindingDied(null /*null*/); - long delay = mTestImsServiceController.getRebindDelay(); + long delay = REBIND_RETRY.getStartDelay(); waitForHandlerActionDelayed(mHandler, delay, 2 * delay); // The service should autobind after rebind event occurs verify(mMockContext, times(2)).bindService(any(), any(), anyInt()); @@ -1330,7 +1338,7 @@ public class ImsServiceControllerTest extends ImsTestBase { // null binding should be ignored in this case. conn.onNullBinding(null); - long delay = mTestImsServiceController.getRebindDelay(); + long delay = REBIND_RETRY.getStartDelay(); waitForHandlerActionDelayed(mHandler, delay, 2 * delay); // The service should autobind after rebind event occurs verify(mMockContext, times(2)).bindService(any(), any(), anyInt()); @@ -1398,10 +1406,11 @@ public class ImsServiceControllerTest extends ImsTestBase { slotIdToSubIdMap.put(SLOT_0, SUB_2); ServiceConnection conn = bindAndConnectService(testFeatures, slotIdToSubIdMap.clone()); conn.onBindingDied(null /*null*/); - mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap.clone()); - long delay = mTestImsServiceController.getRebindDelay(); + long delay = REBIND_RETRY.getStartDelay(); waitForHandlerActionDelayed(mHandler, delay, 2 * delay); + mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap.clone()); + // Should only see two binds, not three from the auto rebind that occurs. verify(mMockContext, times(2)).bindService(any(), any(), anyInt()); } @@ -1481,6 +1490,9 @@ public class ImsServiceControllerTest extends ImsTestBase { IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class); when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder); connection.onServiceConnected(mTestComponentName, controllerStub); + + long delay = mTestImsServiceController.getRebindDelay(); + waitForHandlerActionDelayed(mHandler, delay, 2 * delay); return connection; } diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java index 63fcf10e7c63ca8251b7a360c005376da9bbbd71..c6b0fa1cc6ce6c7aa1dde236fc45e0cd851a5c25 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java @@ -21,6 +21,8 @@ import static org.junit.Assert.fail; import android.content.Context; import android.os.Handler; import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; import android.testing.TestableLooper; import androidx.test.InstrumentationRegistry; @@ -29,6 +31,7 @@ import com.android.internal.telephony.TelephonyTest; import org.mockito.MockitoAnnotations; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -38,6 +41,22 @@ import java.util.concurrent.TimeUnit; * Helper class to load Mockito Resources into a test. */ public class ImsTestBase { + private static final Field MESSAGE_QUEUE_FIELD; + private static final Field MESSAGE_WHEN_FIELD; + private static final Field MESSAGE_NEXT_FIELD; + + static { + try { + MESSAGE_QUEUE_FIELD = MessageQueue.class.getDeclaredField("mMessages"); + MESSAGE_QUEUE_FIELD.setAccessible(true); + MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); + MESSAGE_WHEN_FIELD.setAccessible(true); + MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); + MESSAGE_NEXT_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Failed to initialize TelephonyTest", e); + } + } protected Context mContext; protected List mTestableLoopers = new ArrayList<>(); @@ -111,6 +130,52 @@ public class ImsTestBase { } } + /** + * @return The longest delay from all the message queues. + */ + private long getLongestDelay() { + long delay = 0; + for (TestableLooper looper : mTestableLoopers) { + MessageQueue queue = looper.getLooper().getQueue(); + try { + Message msg = (Message) MESSAGE_QUEUE_FIELD.get(queue); + while (msg != null) { + delay = Math.max(msg.getWhen(), delay); + msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in TelephonyTest", e); + } + } + return delay; + } + + /** + * @return {@code true} if there are any messages in the queue. + */ + private boolean messagesExist() { + for (TestableLooper looper : mTestableLoopers) { + MessageQueue queue = looper.getLooper().getQueue(); + try { + Message msg = (Message) MESSAGE_QUEUE_FIELD.get(queue); + if (msg != null) return true; + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in TelephonyTest", e); + } + } + return false; + } + + /** + * Handle all messages including the delayed messages. + */ + public void processAllFutureMessages() { + while (messagesExist()) { + moveTimeForward(getLongestDelay()); + processAllMessages(); + } + } + /** * Check if there are any messages to be processed in any monitored TestableLooper * Delayed messages to be handled at a later time will be ignored @@ -123,4 +188,28 @@ public class ImsTestBase { } return true; } + + /** + * Effectively moves time forward by reducing the time of all messages + * for all monitored TestableLoopers + * @param milliSeconds number of milliseconds to move time forward by + */ + public void moveTimeForward(long milliSeconds) { + for (TestableLooper looper : mTestableLoopers) { + MessageQueue queue = looper.getLooper().getQueue(); + try { + Message msg = (Message) MESSAGE_QUEUE_FIELD.get(queue); + while (msg != null) { + long updatedWhen = msg.getWhen() - milliSeconds; + if (updatedWhen < 0) { + updatedWhen = 0; + } + MESSAGE_WHEN_FIELD.set(msg, updatedWhen); + msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in TelephonyTest", e); + } + } + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallInfoTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallInfoTrackerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e3fc6d3c75481b423ab57091335f04fedd93b215 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallInfoTrackerTest.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.telephony.imsphone; + +import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN; + +import static junit.framework.Assert.assertNotNull; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.telephony.ServiceState; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.Call; +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 java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ImsCallInfoTrackerTest extends TelephonyTest { + + private ImsCallInfoTracker mImsCallInfoTracker; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + + mImsCallInfoTracker = new ImsCallInfoTracker(mImsPhone); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testDialingNormalCall() throws Exception { + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ImsPhoneConnection c = getConnection(Call.State.DIALING, false); + mImsCallInfoTracker.addImsCallStatus(c); + + verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any()); + + List imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + ImsCallInfo info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.DIALING, info.getCallState()); + assertFalse(info.isIncoming()); + assertFalse(info.isEmergencyCall()); + assertEquals(EUTRAN, info.getCallRadioTech()); + assertFalse(info.isHeldByRemote()); + } + + @Test + public void testDialingEmergencyCall() throws Exception { + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ImsPhoneConnection c = getConnection(Call.State.DIALING, true); + mImsCallInfoTracker.addImsCallStatus(c); + + verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any()); + + List imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + ImsCallInfo info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.DIALING, info.getCallState()); + assertFalse(info.isIncoming()); + assertTrue(info.isEmergencyCall()); + assertEquals(EUTRAN, info.getCallRadioTech()); + assertFalse(info.isHeldByRemote()); + } + + @Test + public void testIncomingCall() throws Exception { + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ImsPhoneConnection c = getConnection(Call.State.INCOMING, false); + mImsCallInfoTracker.addImsCallStatus(c); + + verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any()); + + List imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + ImsCallInfo info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.INCOMING, info.getCallState()); + assertTrue(info.isIncoming()); + assertFalse(info.isEmergencyCall()); + assertEquals(EUTRAN, info.getCallRadioTech()); + assertFalse(info.isHeldByRemote()); + + // Answer the call + doReturn(Call.State.ACTIVE).when(c).getState(); + mImsCallInfoTracker.updateImsCallStatus(c); + + verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.ACTIVE, info.getCallState()); + + // Hold the call + doReturn(Call.State.HOLDING).when(c).getState(); + mImsCallInfoTracker.updateImsCallStatus(c); + + verify(mImsPhone, times(3)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.HOLDING, info.getCallState()); + + // Disconnect the call + doReturn(Call.State.DISCONNECTING).when(c).getState(); + mImsCallInfoTracker.updateImsCallStatus(c); + + verify(mImsPhone, times(4)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.DISCONNECTING, info.getCallState()); + + // Call disconnected + doReturn(Call.State.DISCONNECTED).when(c).getState(); + mImsCallInfoTracker.updateImsCallStatus(c); + + verify(mImsPhone, times(5)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.IDLE, info.getCallState()); + } + + @Test + public void testMultiCalls() throws Exception { + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ImsPhoneConnection c1 = getConnection(Call.State.INCOMING, false); + mImsCallInfoTracker.addImsCallStatus(c1); + + verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any()); + + doReturn(Call.State.ACTIVE).when(c1).getState(); + mImsCallInfoTracker.updateImsCallStatus(c1); + + verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any()); + + List imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + // 1st call + ImsCallInfo info1 = imsCallInfos.get(0); + + assertNotNull(info1); + assertEquals(1, info1.getIndex()); + assertEquals(Call.State.ACTIVE, info1.getCallState()); + + // Add 2nd WAITING call + ImsPhoneConnection c2 = getConnection(Call.State.WAITING, false); + mImsCallInfoTracker.addImsCallStatus(c2); + + verify(mImsPhone, times(3)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(2, imsCallInfos.size()); + + // 1st call + info1 = imsCallInfos.get(0); + + assertNotNull(info1); + assertEquals(1, info1.getIndex()); + assertEquals(Call.State.ACTIVE, info1.getCallState()); + + // 2nd call + ImsCallInfo info2 = imsCallInfos.get(1); + + assertNotNull(info2); + assertEquals(2, info2.getIndex()); + assertEquals(Call.State.WAITING, info2.getCallState()); + assertTrue(info2.isIncoming()); + + // Disconnect 1st call + doReturn(Call.State.DISCONNECTED).when(c1).getState(); + mImsCallInfoTracker.updateImsCallStatus(c1); + + verify(mImsPhone, times(4)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(2, imsCallInfos.size()); + + // 1st call + info1 = imsCallInfos.get(0); + + assertNotNull(info1); + assertEquals(1, info1.getIndex()); + assertEquals(Call.State.IDLE, info1.getCallState()); + + // 2nd call + info2 = imsCallInfos.get(1); + + assertNotNull(info2); + assertEquals(2, info2.getIndex()); + assertEquals(Call.State.WAITING, info2.getCallState()); + + // Answer WAITING call + doReturn(Call.State.ACTIVE).when(c2).getState(); + mImsCallInfoTracker.updateImsCallStatus(c2); + + verify(mImsPhone, times(5)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + // 2nd call + info2 = imsCallInfos.get(0); + + assertNotNull(info2); + assertEquals(2, info2.getIndex()); + assertEquals(Call.State.ACTIVE, info2.getCallState()); + } + + @Test + public void testHeldByRemote() throws Exception { + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ImsPhoneConnection c = getConnection(Call.State.INCOMING, false); + mImsCallInfoTracker.addImsCallStatus(c); + + verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any()); + + doReturn(Call.State.ACTIVE).when(c).getState(); + mImsCallInfoTracker.updateImsCallStatus(c); + + verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any()); + + // Hold received + mImsCallInfoTracker.updateImsCallStatus(c, true, false); + + verify(mImsPhone, times(3)).updateImsCallStatus(captor.capture(), any()); + + List imsCallInfos = captor.getValue(); + + assertEquals(1, imsCallInfos.size()); + + ImsCallInfo info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.ACTIVE, info.getCallState()); + assertTrue(info.isHeldByRemote()); + + // Resume received + mImsCallInfoTracker.updateImsCallStatus(c, false, true); + + verify(mImsPhone, times(4)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertEquals(1, imsCallInfos.size()); + + info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.ACTIVE, info.getCallState()); + assertFalse(info.isHeldByRemote()); + } + + @Test + public void testSortImsCallInfo() throws Exception { + List imsCallInfos = new ArrayList<>(); + imsCallInfos.add(new ImsCallInfo(2)); + imsCallInfos.add(new ImsCallInfo(1)); + + assertEquals(2, imsCallInfos.get(0).getIndex()); + assertEquals(1, imsCallInfos.get(1).getIndex()); + + ImsCallInfoTracker.sort(imsCallInfos); + + assertEquals(1, imsCallInfos.get(0).getIndex()); + assertEquals(2, imsCallInfos.get(1).getIndex()); + } + + @Test + public void testSrvccCompleted() throws Exception { + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ImsPhoneConnection c = getConnection(Call.State.DIALING, false); + mImsCallInfoTracker.addImsCallStatus(c); + + verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any()); + + List imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + mImsCallInfoTracker.notifySrvccCompleted(); + + verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(0, imsCallInfos.size()); + } + + @Test + public void testClearAllOrphanedConnections() throws Exception { + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ImsPhoneConnection c = getConnection(Call.State.DIALING, false); + mImsCallInfoTracker.addImsCallStatus(c); + + verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any()); + + List imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + mImsCallInfoTracker.clearAllOrphanedConnections(); + + verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any()); + + imsCallInfos = captor.getValue(); + + assertNotNull(imsCallInfos); + assertEquals(1, imsCallInfos.size()); + + ImsCallInfo info = imsCallInfos.get(0); + + assertNotNull(info); + assertEquals(1, info.getIndex()); + assertEquals(Call.State.IDLE, info.getCallState()); + } + + private ImsPhoneConnection getConnection(Call.State state, boolean isEmergency) { + ImsPhoneConnection c = mock(ImsPhoneConnection.class); + doReturn(isEmergency).when(c).isEmergencyCall(); + doReturn(state).when(c).getState(); + doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(c).getCallRadioTech(); + switch (state) { + case INCOMING: + case WAITING: + doReturn(true).when(c).isIncoming(); + break; + default: + doReturn(false).when(c).isIncoming(); + } + + return c; + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7d6557dc3c6b518edef613052b4f4b56f0aaaf33 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java @@ -0,0 +1,384 @@ +/* + * 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.imsphone; + +import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA; +import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA; +import static android.telephony.CarrierConfigManager.Ims.KEY_NR_SA_DISABLE_POLICY_INT; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_NONE; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED; +import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED; +import static android.telephony.CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE; + +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.doReturn; +import static org.mockito.Mockito.verify; + +import android.os.Handler; +import android.telephony.CarrierConfigManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.ArraySet; + +import com.android.internal.telephony.Call; +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.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Set; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public final class ImsNrSaModeHandlerTest extends TelephonyTest{ + @Captor ArgumentCaptor + mCarrierConfigChangeListenerCaptor; + @Captor ArgumentCaptor mPreciseCallStateHandlerCaptor; + + private ImsNrSaModeHandler mTestImsNrSaModeHandler; + private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; + private Handler mPreciseCallStateHandler; + + @Mock private ImsPhoneCall mForegroundCall; + @Mock private ImsPhoneCall mBackgroundCall; + private Call.State mActiveState = ImsPhoneCall.State.ACTIVE; + private Call.State mIdleState = ImsPhoneCall.State.IDLE; + + private int mAnyInt = 0; + private final Set mFeatureTags = + new ArraySet(Arrays.asList(ImsNrSaModeHandler.MMTEL_FEATURE_TAG)); + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + + mTestImsNrSaModeHandler = new ImsNrSaModeHandler(mImsPhone, mTestableLooper.getLooper()); + + verify(mCarrierConfigManager).registerCarrierConfigChangeListener( + any(), mCarrierConfigChangeListenerCaptor.capture()); + + mCarrierConfigChangeListener = mCarrierConfigChangeListenerCaptor.getValue(); + + doReturn(mAnyInt).when(mImsPhone).getSubId(); + doReturn(mContextFixture.getCarrierConfigBundle()).when(mCarrierConfigManager) + .getConfigForSubId(anyInt(), any()); + doReturn(mPhone).when(mImsPhone).getDefaultPhone(); + + doReturn(mForegroundCall).when(mImsPhone).getForegroundCall(); + doReturn(mBackgroundCall).when(mImsPhone).getBackgroundCall(); + + doReturn(mActiveState).when(mForegroundCall).getState(); + doReturn(mActiveState).when(mBackgroundCall).getState(); + } + + @After + public void tearDown() throws Exception { + mTestImsNrSaModeHandler = null; + super.tearDown(); + } + + @Test + public void testTearDown() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + verify(mImsPhone).registerForPreciseCallStateChanged( + mPreciseCallStateHandlerCaptor.capture(), anyInt(), any()); + mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue(); + + mSimulatedCommands.setN1ModeEnabled(false, null); + mTestImsNrSaModeHandler.setNrSaDisabledForWfc(true); + + mTestImsNrSaModeHandler.tearDown(); + + verify(mCarrierConfigManager).unregisterCarrierConfigChangeListener(any()); + verify(mImsPhone).unregisterForPreciseCallStateChanged(mPreciseCallStateHandler); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + } + + @Test + public void testOnImsRegisteredWithSaDisablePolicyNone() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_NONE); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags); + + assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered()); + } + + @Test + public void testOnImsRegisteredWithSaDisablePolicyWfcEstablished() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + verify(mImsPhone).registerForPreciseCallStateChanged(any(), anyInt(), any()); + + mSimulatedCommands.setN1ModeEnabled(false, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + mTestImsNrSaModeHandler.setImsCallStatus(true); + + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags); + + assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + } + + @Test + public void testOnImsRegisteredWithSaDisablePolicyWfcEstablishedWithVonrDisabled() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, + NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + verify(mImsPhone).registerForPreciseCallStateChanged(any(), anyInt(), any()); + + mSimulatedCommands.setN1ModeEnabled(true, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + mTestImsNrSaModeHandler.setImsCallStatus(true); + mSimulatedCommands.setVonrEnabled(true); + + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags); + processAllMessages(); + + assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + + mSimulatedCommands.setN1ModeEnabled(true, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + mTestImsNrSaModeHandler.setImsCallStatus(true); + mSimulatedCommands.setVonrEnabled(false); + + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags); + processAllMessages(); + + assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertFalse(mSimulatedCommands.isN1ModeEnabled()); + + mSimulatedCommands.setN1ModeEnabled(true, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + mTestImsNrSaModeHandler.setImsCallStatus(true); + mSimulatedCommands.setVonrEnabled(false); + + mFeatureTags.remove(ImsNrSaModeHandler.MMTEL_FEATURE_TAG); + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags); + processAllMessages(); + + assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + + mSimulatedCommands.setN1ModeEnabled(true, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + mTestImsNrSaModeHandler.setImsCallStatus(true); + mSimulatedCommands.setVonrEnabled(false); + + mFeatureTags.add(ImsNrSaModeHandler.MMTEL_FEATURE_TAG); + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags); + processAllMessages(); + + assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertFalse(mSimulatedCommands.isN1ModeEnabled()); + } + + @Test + public void testOnImsRegisteredWithSaDisablePolicyVowifiRegistered() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + mSimulatedCommands.setN1ModeEnabled(true, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags); + + assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertFalse(mSimulatedCommands.isN1ModeEnabled()); + + mSimulatedCommands.setN1ModeEnabled(false, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags); + + assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + } + + @Test + public void testOnImsUnregisteredDoNothingIfNotVowifiRegNoti() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + + mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_NONE); + + assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered()); + } + + @Test + public void testOnImsUnregisteredWithSaDisablePolicyVowifiRegistered() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + mSimulatedCommands.setN1ModeEnabled(false, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + + mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_IWLAN); + + assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + + mSimulatedCommands.setN1ModeEnabled(false, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + + mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_NONE); + + assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertFalse(mSimulatedCommands.isN1ModeEnabled()); + } + + @Test + public void testOnPreciseCallStateChangedWithSaDisablePolicyWfcEstablished() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + verify(mImsPhone).registerForPreciseCallStateChanged( + mPreciseCallStateHandlerCaptor.capture(), anyInt(), any()); + mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue(); + + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + mSimulatedCommands.setN1ModeEnabled(true, null); + + mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101)); + + assertTrue(mTestImsNrSaModeHandler.isImsCallOngoing()); + assertFalse(mSimulatedCommands.isN1ModeEnabled()); + + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + mSimulatedCommands.setN1ModeEnabled(true, null); + + doReturn(mActiveState).when(mForegroundCall).getState(); + doReturn(mActiveState).when(mBackgroundCall).getState(); + mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101)); + + assertTrue(mTestImsNrSaModeHandler.isImsCallOngoing()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + + mTestImsNrSaModeHandler.setVowifiRegStatus(false); + mTestImsNrSaModeHandler.setImsCallStatus(false); + mSimulatedCommands.setN1ModeEnabled(true, null); + + doReturn(mIdleState).when(mForegroundCall).getState(); + doReturn(mIdleState).when(mBackgroundCall).getState(); + mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101)); + + assertFalse(mTestImsNrSaModeHandler.isImsCallOngoing()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + mTestImsNrSaModeHandler.setImsCallStatus(true); + mSimulatedCommands.setN1ModeEnabled(false, null); + mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101)); + + assertFalse(mTestImsNrSaModeHandler.isImsCallOngoing()); + assertTrue(mSimulatedCommands.isN1ModeEnabled()); + } + + @Test + public void testUnregisterForPreciseCallStateChangeIfNeeded() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + verify(mImsPhone).registerForPreciseCallStateChanged( + mPreciseCallStateHandlerCaptor.capture(), anyInt(), any()); + mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue(); + + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + verify(mImsPhone).unregisterForPreciseCallStateChanged(mPreciseCallStateHandler); + } + + @Test + public void testNrSaModeIsNotHandledWhenNotSupported() { + mContextFixture.getCarrierConfigBundle().putInt( + KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED); + mContextFixture.getCarrierConfigBundle().putIntArray( + KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_NSA}); + + mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt); + + mSimulatedCommands.setN1ModeEnabled(false, null); + mTestImsNrSaModeHandler.setVowifiRegStatus(true); + + mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags); + + assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered()); + assertFalse(mSimulatedCommands.isN1ModeEnabled()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java index c2db93ff6c23439092ec72479225b15db4cb51e4..c4bb8641b5d1cba285a5f1e9162ffba45f6626c9 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.imsphone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; @@ -27,6 +28,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsStreamMediaProfile; import android.test.suitebuilder.annotation.SmallTest; @@ -256,6 +259,51 @@ public class ImsPhoneCallTest extends TelephonyTest { assertEquals(mImsCall, imsCall); } + @Test + @SmallTest + public void testGetCallSessionId() { + doReturn(mImsCall).when(mConnection1).getImsCall(); + ImsCallSession imsForegroundCallSession = mock(ImsCallSession.class); + doReturn(imsForegroundCallSession).when(mImsCall).getSession(); + doReturn("1").when(imsForegroundCallSession).getCallId(); + mImsCallUT.attach(mConnection1, Call.State.ACTIVE); + assertEquals("1", mImsCallUT.getCallSessionId()); + doReturn(null).when(mImsCall).getSession(); + assertNull(mImsCallUT.getCallSessionId()); + doReturn(null).when(mConnection1).getImsCall(); + assertNull(mImsCallUT.getCallSessionId()); + mImsCallUT.detach(mConnection1); + assertNull(mImsCallUT.getCallSessionId()); + } + + @Test + @SmallTest + public void testGetServiceType() { + doReturn(mImsCall).when(mConnection1).getImsCall(); + mImsCallUT.attach(mConnection1, Call.State.ACTIVE); + doReturn(false).when(mConnection1).isEmergencyCall(); + assertEquals(ImsCallProfile.SERVICE_TYPE_NORMAL, mImsCallUT.getServiceType()); + doReturn(true).when(mConnection1).isEmergencyCall(); + assertEquals(ImsCallProfile.SERVICE_TYPE_EMERGENCY, mImsCallUT.getServiceType()); + mImsCallUT.detach(mConnection1); + assertEquals(ImsCallProfile.SERVICE_TYPE_NONE, mImsCallUT.getServiceType()); + } + + @Test + @SmallTest + public void testGetCallType() { + doReturn(mImsCall).when(mConnection1).getImsCall(); + mImsCallUT.attach(mConnection1, Call.State.ACTIVE); + doReturn(false).when(mImsCall).isVideoCall(); + assertEquals(ImsCallProfile.CALL_TYPE_VOICE, mImsCallUT.getCallType()); + doReturn(true).when(mImsCall).isVideoCall(); + assertEquals(ImsCallProfile.CALL_TYPE_VT, mImsCallUT.getCallType()); + doReturn(null).when(mConnection1).getImsCall(); + assertEquals(ImsCallProfile.CALL_TYPE_NONE, mImsCallUT.getCallType()); + mImsCallUT.detach(mConnection1); + assertEquals(ImsCallProfile.CALL_TYPE_NONE, mImsCallUT.getCallType()); + } + @Test @SmallTest public void testSetMute() { @@ -269,4 +317,24 @@ public class ImsPhoneCallTest extends TelephonyTest { fail("Exception unexpected"); } } + + @Test + public void testMaybeClearRemotelyHeldStatus() { + mImsCallUT.attach(mConnection1, Call.State.ACTIVE); + when(mConnection1.isHeldByRemote()).thenReturn(true); + mImsCallUT.maybeClearRemotelyHeldStatus(); + verify(mConnection1, times(1)).setRemotelyUnheld(); + + mImsCallUT.attach(mConnection2, Call.State.ACTIVE); + when(mConnection2.isHeldByRemote()).thenReturn(true); + mImsCallUT.maybeClearRemotelyHeldStatus(); + verify(mConnection1, times(2)).setRemotelyUnheld(); + verify(mConnection2, times(1)).setRemotelyUnheld(); + + when(mConnection1.isHeldByRemote()).thenReturn(false); + when(mConnection2.isHeldByRemote()).thenReturn(false); + mImsCallUT.maybeClearRemotelyHeldStatus(); + verify(mConnection1, times(2)).setRemotelyUnheld(); + verify(mConnection2, times(1)).setRemotelyUnheld(); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java index 906709acd23964960efdf741b9725233440b8246..d0a20949bbfd378a662e0f5b3390f95dcd985634 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java @@ -21,6 +21,23 @@ import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.SET_FOREGROUND; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; +import static android.telephony.CarrierConfigManager.ImsVoice.ALERTING_SRVCC_SUPPORT; +import static android.telephony.CarrierConfigManager.ImsVoice.BASIC_SRVCC_SUPPORT; +import static android.telephony.CarrierConfigManager.ImsVoice.MIDCALL_SRVCC_SUPPORT; +import static android.telephony.CarrierConfigManager.ImsVoice.PREALERTING_SRVCC_SUPPORT; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING; +import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING_SETUP; +import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED; +import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED; +import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_FAILED; +import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_STARTED; +import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE; +import static android.telephony.ims.ImsStreamMediaProfile.DIRECTION_INACTIVE; +import static android.telephony.ims.ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; +import static android.telephony.ims.feature.MmTelFeature.IMS_TRAFFIC_DIRECTION_INCOMING; +import static android.telephony.ims.feature.MmTelFeature.IMS_TRAFFIC_DIRECTION_OUTGOING; import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals; @@ -57,17 +74,20 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.NetworkStats; import android.net.NetworkStats.Entry; +import android.net.Uri; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.Bundle; import android.os.Message; import android.os.PersistableBundle; import android.os.RemoteException; import android.telecom.VideoProfile; +import android.telephony.AccessNetworkConstants; import android.telephony.CarrierConfigManager; import android.telephony.DisconnectCause; import android.telephony.PhoneNumberUtils; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsConferenceState; @@ -75,6 +95,9 @@ import android.telephony.ims.ImsMmTelManager; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.telephony.ims.RtpHeaderExtensionType; +import android.telephony.ims.SrvccCall; +import android.telephony.ims.aidl.IImsTrafficSessionCallback; +import android.telephony.ims.aidl.ISrvccStartedCallback; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -90,15 +113,19 @@ import com.android.ims.ImsCall; import com.android.ims.ImsConfig; import com.android.ims.ImsException; import com.android.ims.ImsManager; +import com.android.ims.internal.ConferenceParticipant; import com.android.ims.internal.IImsCallSession; import com.android.internal.telephony.Call; import com.android.internal.telephony.CallStateException; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.Connection; import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.SrvccConnection; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.d2d.RtpTransport; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; import com.android.internal.telephony.imsphone.ImsPhoneCallTracker.VtDataUsageProvider; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; @@ -109,9 +136,12 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.concurrent.Executor; @@ -125,6 +155,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { private ImsCall.Listener mImsCallListener; private ImsCall mImsCall; private ImsCall mSecondImsCall; + private ISrvccStartedCallback mSrvccStartedCallback; private BroadcastReceiver mBroadcastReceiver; private Bundle mBundle = new Bundle(); private static final int SUB_0 = 0; @@ -140,6 +171,8 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { private ImsPhoneConnection mImsPhoneConnection; private INetworkStatsProviderCallback mVtDataUsageProviderCb; private ImsPhoneCallTracker.ConnectorFactory mConnectorFactory; + private CommandsInterface mMockCi; + private DomainSelectionResolver mDomainSelectionResolver; private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; private final Executor mExecutor = Runnable::run; @@ -201,11 +234,13 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile)); mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class); mImsPhoneConnection = mock(ImsPhoneConnection.class); + mMockCi = mock(CommandsInterface.class); imsCallMocking(mImsCall); imsCallMocking(mSecondImsCall); doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceState(); doReturn(mImsCallProfile).when(mImsManager).createCallProfile(anyInt(), anyInt()); mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS); + mDomainSelectionResolver = mock(DomainSelectionResolver.class); doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1).build()) .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); @@ -244,6 +279,9 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { return mMockConnector; }).when(mConnectorFactory).create(any(), anyInt(), anyString(), any(), any()); + DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver); + doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported(); + // Capture CarrierConfigChangeListener to emulate the carrier config change notification ArgumentCaptor listenerArgumentCaptor = ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class); @@ -357,16 +395,11 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { @Test @SmallTest public void testCarrierConfigLoadSubscription() throws Exception { - // Start with there being no subId loaded, so SubscriptionController#isActiveSubId is false - // as part of setup, connectionReady is called, which ends up calling - // updateCarrierConfiguration. Since the carrier config is not report carrier identified - // config, we should not see updateImsServiceConfig called yet. verify(mImsManager, never()).updateImsServiceConfig(); // Send disconnected indication mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); // Receive a subscription loaded and IMS connection ready indication. - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); sendCarrierConfigChanged(); @@ -382,7 +415,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { public void testCarrierConfigSentLocked() throws Exception { // move to ImsService unavailable state. mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); @@ -409,7 +441,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { verify(mImsManager, never()).updateImsServiceConfig(); // Receive a subscription loaded and IMS connection ready indication. - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); // CarrierConfigLoader has signalled that the carrier config has been applied for a specific @@ -429,7 +460,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { public void testCarrierConfigSentBeforeReady() throws Exception { // move to ImsService unavailable state. mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); @@ -450,7 +480,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { verify(mImsManager, never()).updateImsServiceConfig(); // Receive a subscription loaded and IMS connection ready indication. - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); // CarrierConfigLoader has signalled that the carrier config has been applied for a specific @@ -474,7 +503,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { public void testCarrierConfigSentBeforeReadyAndCrash() throws Exception { // move to ImsService unavailable state. mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); @@ -542,7 +570,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { assertEquals(PhoneConstants.State.IDLE, mCTUT.getState()); assertFalse(mCTUT.mRingingCall.isRinging()); // mock a MT call - mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY); + mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY); verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any()); verify(mImsPhone, times(1)).notifyIncomingRing(); assertEquals(PhoneConstants.State.RINGING, mCTUT.getState()); @@ -694,7 +722,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { ex.printStackTrace(); Assert.fail("unexpected exception thrown" + ex.getMessage()); } - mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY); + mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY); verify(mImsPhone, times(2)).notifyNewRingingConnection((Connection) any()); verify(mImsPhone, times(2)).notifyIncomingRing(); @@ -732,7 +760,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { ex.printStackTrace(); Assert.fail("unexpected exception thrown" + ex.getMessage()); } - mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY); + mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY); verify(mImsPhone, times(2)).notifyNewRingingConnection((Connection) any()); verify(mImsPhone, times(2)).notifyIncomingRing(); @@ -924,7 +952,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { try { doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class), any(ImsCall.Listener.class)); - mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY); + mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY); mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE); } catch (Exception ex) { ex.printStackTrace(); @@ -1395,6 +1423,43 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { new ImsReasonInfo(ImsReasonInfo.CODE_LOW_BATTERY, 0), Call.State.ACTIVE)); } + @Test + @SmallTest + public void testAutoRejectedCauses() { + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_CALL_ON_OTHER_SUB, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_E911_CALL, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CALL_SETUP, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_MAX_CALL_LIMIT_REACHED, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CALL_TRANSFER, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_HANDOVER, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CALL_UPGRADE, 0), + Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.INCOMING)); + assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.WAITING)); + assertEquals(DisconnectCause.SERVER_ERROR, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.DIALING)); + assertEquals(DisconnectCause.SERVER_ERROR, mCTUT.getDisconnectCauseFromReasonInfo( + new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.ALERTING)); + } + @Test @SmallTest public void testImsAlternateEmergencyDisconnect() { @@ -1439,6 +1504,45 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { verify(mImsPhone, never()).startOnHoldTone(nullable(Connection.class)); } + @Test + @SmallTest + public void testSendAnbrQuery() throws Exception { + logd("ImsPhoneCallTracker testSendAnbrQuery"); + + replaceInstance(Phone.class, "mCi", mPhone, mMockCi); + //establish a MT call + testImsMTCallAccept(); + + ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection(); + ImsCall imsCall = connection.getImsCall(); + imsCall.getImsCallSessionListenerProxy().callSessionSendAnbrQuery(1, 1, 24400); + + verify(mMockCi, times(1)).sendAnbrQuery(eq(1), eq(1), eq(24400), any()); + + // Disconnecting and then Disconnected + mCTUT.hangup(connection); + mImsCallListener.onCallTerminated(imsCall, + new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0)); + } + + @Test + @SmallTest + public void testTriggerNotifyAnbr() throws Exception { + logd("ImsPhoneCallTracker testTriggerNotifyAnbr"); + + testImsMTCallAccept(); + ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection(); + ImsCall imsCall = connection.getImsCall(); + + mCTUT.triggerNotifyAnbr(1, 1, 24400); + verify(mImsCall, times(1)).callSessionNotifyAnbr(eq(1), eq(1), eq(24400)); + + // Disconnecting and then Disconnected + mCTUT.hangup(connection); + mImsCallListener.onCallTerminated(imsCall, + new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0)); + } + /** * Verifies that a remote hold tone is played when the call is remotely held and the media * direction is inactive (i.e. the audio stream is not playing, so we should play the tone). @@ -1589,7 +1693,9 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { } }); ImsCall call = connection.getImsCall(); - call.getListener().onCallMerged(call, null, false); + call.getListener().onCallTerminated( + call, new ImsReasonInfo( + ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE, 0)); assertTrue(result[0]); } @@ -1699,7 +1805,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { assertTrue(mCTUT.mForegroundCall.isRingbackTonePlaying()); // Move the connection to the handover state. - mCTUT.notifySrvccState(Call.SrvccState.COMPLETED); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED); assertFalse(mCTUT.mForegroundCall.isRingbackTonePlaying()); } @@ -1730,7 +1836,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { } // Move the connection to the handover state. - mCTUT.notifySrvccState(Call.SrvccState.COMPLETED); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED); // Ensure we are no longer tracking hold. assertFalse(mCTUT.isHoldOrSwapInProgress()); } @@ -1742,7 +1848,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { assertEquals(PhoneConstants.State.IDLE, mCTUT.getState()); assertFalse(mCTUT.mRingingCall.isRinging()); // mock a MT call - mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY); + mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY); verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any()); verify(mImsPhone, times(1)).notifyIncomingRing(); assertEquals(PhoneConstants.State.RINGING, mCTUT.getState()); @@ -1753,7 +1859,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { connection.addListener(mImsPhoneConnectionListener); // Move the connection to the handover state. - mCTUT.notifySrvccState(Call.SrvccState.COMPLETED); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED); assertEquals(1, mCTUT.mHandoverCall.getConnections().size()); // No need to go through all the rigamarole of the mocked termination we normally do; we @@ -1780,7 +1886,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { assertEquals(PhoneConstants.State.IDLE, mCTUT.getState()); assertFalse(mCTUT.mRingingCall.isRinging()); // mock a MT call - mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY); + mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY); verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any()); verify(mImsPhone, times(1)).notifyIncomingRing(); assertEquals(PhoneConstants.State.RINGING, mCTUT.getState()); @@ -1791,7 +1897,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { connection.addListener(mImsPhoneConnectionListener); // Move the connection to the handover state. - mCTUT.notifySrvccState(Call.SrvccState.COMPLETED); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED); assertEquals(1, mCTUT.mHandoverCall.getConnections().size()); // Make sure the tracker states it's idle. @@ -1807,7 +1913,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { @SmallTest public void testConfigureRtpHeaderExtensionTypes() throws Exception { mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL, true); @@ -1838,7 +1943,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { @SmallTest public void testRtpButNoSdp() throws Exception { mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); mContextFixture.getCarrierConfigBundle().putBoolean( CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL, true); @@ -1868,7 +1972,6 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { @SmallTest public void testDontConfigureRtpHeaderExtensionTypes() throws Exception { mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); - doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt()); sendCarrierConfigChanged(); ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config(); config.isD2DCommunicationSupported = false; @@ -1902,7 +2005,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { startOutgoingCall(); // Move the connection to the handover state. - mCTUT.notifySrvccState(Call.SrvccState.COMPLETED); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED); try { // When trigger CallSessionUpdated after Srvcc completes, checking no exception. @@ -1936,6 +2039,608 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { ImsPhoneConnection connection2 = placeCall(); } + @Test + @SmallTest + public void testConvertToSrvccConnectionInfoNotSupported() throws Exception { + // setup ImsPhoneCallTracker's mConnections + ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false); + ImsPhoneConnection heldMT = getImsPhoneConnection(Call.State.HOLDING, "5678", true); + + ArrayList connections = new ArrayList(); + replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections); + connections.add(activeMO); + connections.add(heldMT); + + ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false); + ImsCallProfile heldProfile = getImsCallProfileForSrvccSync("heldCall", heldMT, false); + + // setup the response of notifySrvccStarted + List profiles = new ArrayList<>(); + + SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNull(srvccConnections); + + // active call + SrvccCall srvccProfile = new SrvccCall( + "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile); + profiles.add(srvccProfile); + + PersistableBundle bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] {}); + mCTUT.updateCarrierConfigCache(bundle); + + srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNull(srvccConnections); + } + + @Test + @SmallTest + public void testConvertToSrvccConnectionInfoBasicSrvcc() throws Exception { + // setup ImsPhoneCallTracker's mConnections + ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false); + ImsPhoneConnection heldMT = getImsPhoneConnection(Call.State.HOLDING, "5678", true); + + ArrayList connections = new ArrayList(); + replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections); + connections.add(activeMO); + connections.add(heldMT); + + ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false); + ImsCallProfile heldProfile = getImsCallProfileForSrvccSync("heldCall", heldMT, false); + + // setup the response of notifySrvccStarted + List profiles = new ArrayList<>(); + + // active call + SrvccCall srvccProfile = new SrvccCall( + "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile); + profiles.add(srvccProfile); + + PersistableBundle bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 1); + assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE); + assertEquals("1234", srvccConnections[0].getNumber()); + } + + @Test + @SmallTest + public void testConvertToSrvccConnectionInfoMoAlerting() throws Exception { + // setup ImsPhoneCallTracker's mConnections + ImsPhoneConnection alertingMO = getImsPhoneConnection(Call.State.ALERTING, "1234", false); + + ArrayList connections = new ArrayList(); + replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections); + connections.add(alertingMO); + + ImsCallProfile alertingProfile = getImsCallProfileForSrvccSync("alertingCall", null, true); + + // setup the response of notifySrvccStarted + List profiles = new ArrayList<>(); + + // alerting call, with local ringback tone + SrvccCall srvccProfile = new SrvccCall( + "alertingCall", PRECISE_CALL_STATE_ALERTING, alertingProfile); + profiles.add(srvccProfile); + + PersistableBundle bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNull(srvccConnections); + + bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + ALERTING_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 1); + assertTrue(srvccConnections[0].getState() == Call.State.ALERTING); + assertTrue(srvccConnections[0].getRingbackToneType() == SrvccConnection.TONE_LOCAL); + + profiles.clear(); + + // alerting call, with network ringback tone + alertingProfile = getImsCallProfileForSrvccSync("alertingCall", null, false); + + srvccProfile = new SrvccCall( + "alertingCall", PRECISE_CALL_STATE_ALERTING, alertingProfile); + profiles.add(srvccProfile); + + srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 1); + assertTrue(srvccConnections[0].getState() == Call.State.ALERTING); + assertTrue(srvccConnections[0].getRingbackToneType() == SrvccConnection.TONE_NETWORK); + } + + @Test + @SmallTest + public void testConvertToSrvccConnectionInfoMtAlerting() throws Exception { + // setup ImsPhoneCallTracker's mConnections + ImsPhoneConnection alertingMT = getImsPhoneConnection(Call.State.INCOMING, "1234", false); + + ArrayList connections = new ArrayList(); + replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections); + connections.add(alertingMT); + + ImsCallProfile incomingProfile = + getImsCallProfileForSrvccSync("incomingCall", alertingMT, false); + + // setup the response of notifySrvccStarted + List profiles = new ArrayList<>(); + + SrvccCall srvccProfile = new SrvccCall( + "incomingCall", PRECISE_CALL_STATE_INCOMING, incomingProfile); + profiles.add(srvccProfile); + + PersistableBundle bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNull(srvccConnections); + + bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + ALERTING_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 1); + assertTrue(srvccConnections[0].getState() == Call.State.INCOMING); + } + + @Test + @SmallTest + public void testConvertToSrvccConnectionInfoMtPreAlerting() throws Exception { + // setup the response of notifySrvccStarted + List profiles = new ArrayList<>(); + + ImsCallProfile incomingProfile = getImsCallProfileForSrvccSync("incomingCall", null, false); + + SrvccCall srvccProfile = new SrvccCall( + "incomingCallSetup", PRECISE_CALL_STATE_INCOMING_SETUP, incomingProfile); + profiles.add(srvccProfile); + + PersistableBundle bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + ALERTING_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNull(srvccConnections); + + bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + ALERTING_SRVCC_SUPPORT, + PREALERTING_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 1); + assertTrue(srvccConnections[0].getState() == Call.State.INCOMING); + assertTrue(srvccConnections[0].getSubState() == SrvccConnection.SUBSTATE_PREALERTING); + } + + @Test + @SmallTest + public void testNotifySrvccStateStarted() throws Exception { + PersistableBundle bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + mSrvccStartedCallback = null; + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + mSrvccStartedCallback = (ISrvccStartedCallback) invocation.getArguments()[0]; + return null; + } + }).when(mImsManager).notifySrvccStarted(any(ISrvccStartedCallback.class)); + + verify(mImsManager, times(0)).notifySrvccStarted(any()); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_STARTED); + verify(mImsManager, times(1)).notifySrvccStarted(any()); + assertNotNull(mSrvccStartedCallback); + + // setup ImsPhoneCallTracker's mConnections + ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false); + + ArrayList connections = new ArrayList(); + replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections); + connections.add(activeMO); + + ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false); + + // setup the response of notifySrvccStarted + List profiles = new ArrayList<>(); + + // active call + SrvccCall srvccProfile = new SrvccCall( + "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile); + profiles.add(srvccProfile); + + mSrvccStartedCallback.onSrvccCallNotified(profiles); + SrvccConnection[] srvccConnections = mSimulatedCommands.getSrvccConnections(); + + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 1); + assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE); + assertEquals("1234", srvccConnections[0].getNumber()); + } + + @Test + @SmallTest + public void testNotifySrvccStateFailed() throws Exception { + verify(mImsManager, times(0)).notifySrvccFailed(); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_FAILED); + verify(mImsManager, times(1)).notifySrvccFailed(); + } + + @Test + @SmallTest + public void testNotifySrvccStateCanceled() throws Exception { + verify(mImsManager, times(0)).notifySrvccCanceled(); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_CANCELED); + verify(mImsManager, times(1)).notifySrvccCanceled(); + } + + @Test + @SmallTest + public void testNotifySrvccStateCompleted() throws Exception { + verify(mImsManager, times(0)).notifySrvccCompleted(); + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED); + verify(mImsManager, times(1)).notifySrvccCompleted(); + } + + @Test + @SmallTest + public void testConvertToSrvccConnectionInfoConferenceCall() throws Exception { + // setup ImsPhoneCallTracker's mConnections + ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false); + + ArrayList connections = new ArrayList(); + replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections); + connections.add(activeMO); + + List participants = new ArrayList(); + participants.add(new ConferenceParticipant(Uri.parse("tel:1234"), "", null, + android.telecom.Connection.STATE_ACTIVE, + android.telecom.Call.Details.DIRECTION_INCOMING)); + participants.add(new ConferenceParticipant(Uri.parse("tel:5678"), "", null, + android.telecom.Connection.STATE_ACTIVE, + android.telecom.Call.Details.DIRECTION_OUTGOING)); + + ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", + activeMO, false, participants); + + // setup the response of notifySrvccStarted + List profiles = new ArrayList<>(); + + SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNull(srvccConnections); + + // active call + SrvccCall srvccProfile = new SrvccCall( + "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile); + profiles.add(srvccProfile); + + PersistableBundle bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + }); + mCTUT.updateCarrierConfigCache(bundle); + + srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 1); + assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE); + assertFalse(srvccConnections[0].isMultiParty()); + assertEquals("1234", srvccConnections[0].getNumber()); + + bundle = mContextFixture.getCarrierConfigBundle(); + bundle.putIntArray( + CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY, + new int[] { + BASIC_SRVCC_SUPPORT, + MIDCALL_SRVCC_SUPPORT + }); + mCTUT.updateCarrierConfigCache(bundle); + + srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles); + assertNotNull(srvccConnections); + assertTrue(srvccConnections.length == 2); + + assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE); + assertTrue(srvccConnections[0].isMultiParty()); + assertTrue(srvccConnections[0].isIncoming()); + assertEquals("1234", srvccConnections[0].getNumber()); + + assertTrue(srvccConnections[1].getState() == Call.State.ACTIVE); + assertTrue(srvccConnections[1].isMultiParty()); + assertFalse(srvccConnections[1].isIncoming()); + assertEquals("5678", srvccConnections[1].getNumber()); + } + + /** + * Verifies that the expected access network tech and IMS features are notified + * to ImsPhone when capabilities are changed. + */ + @Test + @SmallTest + public void testUpdateImsRegistrationInfo() { + // LTE is registered. + doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when( + mImsManager).getRegistrationTech(); + + // enable Voice and Video + MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities(); + caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE); + caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO); + mCapabilityCallback.onCapabilitiesStatusChanged(caps); + processAllMessages(); + + verify(mImsPhone, times(1)).updateImsRegistrationInfo( + eq(CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE + | CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO)); + + // enable SMS + caps = new MmTelFeature.MmTelCapabilities(); + caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS); + mCapabilityCallback.onCapabilitiesStatusChanged(caps); + processAllMessages(); + + verify(mImsPhone, times(1)).updateImsRegistrationInfo( + eq(CommandsInterface.IMS_MMTEL_CAPABILITY_SMS)); + } + + @Test + @SmallTest + public void testDomainSelectionAlternateServiceStartFailed() { + doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported(); + startOutgoingCall(); + ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection(); + mImsCallProfile.setEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_AMBULANCE); + mImsCallListener.onCallStartFailed(mSecondImsCall, + new ImsReasonInfo(ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL, -1)); + processAllMessages(); + EmergencyNumber emergencyNumber = c.getEmergencyNumberInfo(); + assertNotNull(emergencyNumber); + assertEquals(EMERGENCY_SERVICE_CATEGORY_AMBULANCE, + emergencyNumber.getEmergencyServiceCategoryBitmask()); + } + + @Test + @SmallTest + public void testDomainSelectionAlternateServiceStartFailedNullPendingMO() { + doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported(); + startOutgoingCall(); + ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection(); + mImsCallListener.onCallProgressing(mSecondImsCall); + processAllMessages(); + mImsCallProfile.setEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_AMBULANCE); + mImsCallListener.onCallStartFailed(mSecondImsCall, + new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED, + ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)); + processAllMessages(); + EmergencyNumber emergencyNumber = c.getEmergencyNumberInfo(); + assertNotNull(emergencyNumber); + assertEquals(EMERGENCY_SERVICE_CATEGORY_AMBULANCE, + emergencyNumber.getEmergencyServiceCategoryBitmask()); + } + + @Test + @SmallTest + public void testDomainSelectionAlternateServiceTerminated() { + doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported(); + startOutgoingCall(); + ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection(); + mImsCallProfile.setEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_AMBULANCE); + mImsCallListener.onCallTerminated(mSecondImsCall, + new ImsReasonInfo(ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL, -1)); + processAllMessages(); + EmergencyNumber emergencyNumber = c.getEmergencyNumberInfo(); + assertNotNull(emergencyNumber); + assertEquals(EMERGENCY_SERVICE_CATEGORY_AMBULANCE, + emergencyNumber.getEmergencyServiceCategoryBitmask()); + } + + @Test + public void testUpdateImsCallStatusIncoming() throws Exception { + // Incoming call + ImsPhoneConnection connection = setupRingingConnection(); + + verify(mImsPhone, times(1)).updateImsCallStatus(any(), any()); + + // Disconnect the call + mImsCallListener.onCallTerminated(connection.getImsCall(), + new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0)); + + verify(mImsPhone, times(2)).updateImsCallStatus(any(), any()); + } + + @Test + public void testUpdateImsCallStatus() throws Exception { + // Dialing + ImsPhoneConnection connection = placeCall(); + + verify(mImsPhone, times(1)).updateImsCallStatus(any(), any()); + + // Alerting + ImsCall imsCall = connection.getImsCall(); + imsCall.getImsCallSessionListenerProxy().callSessionProgressing(imsCall.getSession(), + new ImsStreamMediaProfile()); + + verify(mImsPhone, times(2)).updateImsCallStatus(any(), any()); + + // Active + imsCall.getImsCallSessionListenerProxy().callSessionStarted(imsCall.getSession(), + new ImsCallProfile()); + + verify(mImsPhone, times(3)).updateImsCallStatus(any(), any()); + + // Held by remote + mCTUT.onCallHoldReceived(imsCall); + + verify(mImsPhone, times(4)).updateImsCallStatus(any(), any()); + + // Resumed by remote + mImsCallListener.onCallResumeReceived(imsCall); + + verify(mImsPhone, times(5)).updateImsCallStatus(any(), any()); + + // Disconnecting and then Disconnected + mCTUT.hangup(connection); + mImsCallListener.onCallTerminated(imsCall, + new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0)); + + verify(mImsPhone, times(7)).updateImsCallStatus(any(), any()); + } + + @Test + public void testUpdateImsCallStatusSrvccCompleted() throws Exception { + // Incoming call + setupRingingConnection(); + + verify(mImsPhone, times(1)).updateImsCallStatus(any(), any()); + + // no interaction when SRVCC has started, failed, or canceled. + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_STARTED); + + verify(mImsPhone, times(1)).updateImsCallStatus(any(), any()); + + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_FAILED); + + verify(mImsPhone, times(1)).updateImsCallStatus(any(), any()); + + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_CANCELED); + + verify(mImsPhone, times(1)).updateImsCallStatus(any(), any()); + + // interaction when SRVCC has completed + mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED); + + verify(mImsPhone, times(2)).updateImsCallStatus(any(), any()); + } + + @Test + public void testClearAllOrphanedConnectionInfo() throws Exception { + verify(mImsPhone, times(0)).updateImsCallStatus(any(), any()); + + mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED); + + verify(mImsPhone, times(1)).updateImsCallStatus(any(), any()); + } + + /** Verifies that the request from ImsService is passed to ImsPhone as expected. */ + @Test + @SmallTest + public void testStartAndStopImsTrafficSession() { + IImsTrafficSessionCallback binder = Mockito.mock(IImsTrafficSessionCallback.class); + mMmTelListener.onStartImsTrafficSession(1, MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + IMS_TRAFFIC_DIRECTION_OUTGOING, binder); + verify(mImsPhone, times(1)).startImsTraffic(eq(1), + eq(MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY), + eq(AccessNetworkConstants.AccessNetworkType.EUTRAN), + eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any()); + + mMmTelListener.onStopImsTrafficSession(1); + verify(mImsPhone, times(1)).stopImsTraffic(eq(1), any()); + + mMmTelListener.onStartImsTrafficSession(2, MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS, + AccessNetworkConstants.AccessNetworkType.IWLAN, + IMS_TRAFFIC_DIRECTION_OUTGOING, binder); + verify(mImsPhone, times(1)).startImsTraffic(eq(2), + eq(MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS), + eq(AccessNetworkConstants.AccessNetworkType.IWLAN), + eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any()); + + mMmTelListener.onStartImsTrafficSession(3, MmTelFeature.IMS_TRAFFIC_TYPE_VOICE, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + IMS_TRAFFIC_DIRECTION_INCOMING, binder); + verify(mImsPhone, times(1)).startImsTraffic(eq(3), + eq(MmTelFeature.IMS_TRAFFIC_TYPE_VOICE), + eq(AccessNetworkConstants.AccessNetworkType.EUTRAN), + eq(IMS_TRAFFIC_DIRECTION_INCOMING), any()); + + mMmTelListener.onStartImsTrafficSession(4, MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + IMS_TRAFFIC_DIRECTION_OUTGOING, binder); + verify(mImsPhone, times(1)).startImsTraffic(eq(4), + eq(MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO), + eq(AccessNetworkConstants.AccessNetworkType.EUTRAN), + eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any()); + + mMmTelListener.onStartImsTrafficSession(5, MmTelFeature.IMS_TRAFFIC_TYPE_SMS, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + IMS_TRAFFIC_DIRECTION_OUTGOING, binder); + verify(mImsPhone, times(1)).startImsTraffic(eq(5), + eq(MmTelFeature.IMS_TRAFFIC_TYPE_SMS), + eq(AccessNetworkConstants.AccessNetworkType.EUTRAN), + eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any()); + + mMmTelListener.onStartImsTrafficSession(6, MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + IMS_TRAFFIC_DIRECTION_OUTGOING, binder); + verify(mImsPhone, times(1)).startImsTraffic(eq(6), + eq(MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION), + eq(AccessNetworkConstants.AccessNetworkType.EUTRAN), + eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any()); + + mMmTelListener.onModifyImsTrafficSession(6, + AccessNetworkConstants.AccessNetworkType.IWLAN); + verify(mImsPhone, times(1)).startImsTraffic(eq(6), + eq(MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION), + eq(AccessNetworkConstants.AccessNetworkType.IWLAN), + eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any()); + } + private void sendCarrierConfigChanged() { mCarrierConfigChangeListener.onCarrierConfigChanged(mPhone.getPhoneId(), mPhone.getSubId(), TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID); @@ -2009,5 +2714,45 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest { } return connection; } + + private ImsPhoneConnection getImsPhoneConnection(Call.State state, + String number, boolean isIncoming) { + ImsPhoneCall call = mock(ImsPhoneCall.class); + doReturn(state).when(call).getState(); + + ImsPhoneConnection c = mock(ImsPhoneConnection.class); + doReturn(state).when(c).getState(); + doReturn(isIncoming).when(c).isIncoming(); + doReturn(call).when(c).getCall(); + doReturn(number).when(c).getAddress(); + + return c; + } + + private ImsCallProfile getImsCallProfileForSrvccSync(String callId, + ImsPhoneConnection c, boolean localTone) { + return getImsCallProfileForSrvccSync(callId, c, localTone, null); + } + + private ImsCallProfile getImsCallProfileForSrvccSync(String callId, + ImsPhoneConnection c, boolean localTone, List participants) { + ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(0, + localTone ? DIRECTION_INACTIVE : DIRECTION_SEND_RECEIVE, 0, 0, 0); + ImsCallProfile profile = new ImsCallProfile(0, 0, null, mediaProfile); + + if (c != null) { + ImsCallSession session = mock(ImsCallSession.class); + doReturn(callId).when(session).getCallId(); + + ImsCall imsCall = mock(ImsCall.class); + doReturn(profile).when(imsCall).getCallProfile(); + doReturn(session).when(imsCall).getCallSession(); + doReturn(participants).when(imsCall).getConferenceParticipants(); + + doReturn(imsCall).when(c).getImsCall(); + } + + return profile; + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java index 9779cfa0272b578433330d43f9274f95baa07ffa..396466c8dbe3608a3336174bf0f4e13e08589028 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java @@ -58,6 +58,7 @@ import com.android.internal.telephony.Connection; import com.android.internal.telephony.GsmCdmaCall; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.metrics.VoiceCallSessionStats; @@ -144,7 +145,8 @@ public class ImsPhoneConnectionTest extends TelephonyTest { logd("Testing initial state of MO ImsPhoneConnection"); mConnectionUT = new ImsPhoneConnection(mImsPhone, String.format("+1 (700).555-41NN%c1234", - PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false); + PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false, + new ImsDialArgs.Builder().build()); assertEquals(PhoneConstants.PRESENTATION_ALLOWED, mConnectionUT.getNumberPresentation()); assertEquals(PhoneConstants.PRESENTATION_ALLOWED, mConnectionUT.getCnapNamePresentation()); assertEquals("+1 (700).555-41NN,1234", mConnectionUT.getOrigDialString()); @@ -157,7 +159,7 @@ public class ImsPhoneConnectionTest extends TelephonyTest { public void testImsUpdateStateForeGround() { // MO Foreground Connection dailing -> active mConnectionUT = new ImsPhoneConnection(mImsPhone, "+1 (700).555-41NN1234", mImsCT, - mForeGroundCall, false, false); + mForeGroundCall, false, false, new ImsDialArgs.Builder().build()); // initially in dialing state doReturn(Call.State.DIALING).when(mForeGroundCall).getState(); assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE)); @@ -172,7 +174,7 @@ public class ImsPhoneConnectionTest extends TelephonyTest { public void testUpdateCodec() { // MO Foreground Connection dailing -> active mConnectionUT = new ImsPhoneConnection(mImsPhone, "+1 (700).555-41NN1234", mImsCT, - mForeGroundCall, false, false); + mForeGroundCall, false, false, new ImsDialArgs.Builder().build()); doReturn(Call.State.ACTIVE).when(mForeGroundCall).getState(); assertTrue(mConnectionUT.updateMediaCapabilities(mImsCall)); } @@ -195,7 +197,7 @@ public class ImsPhoneConnectionTest extends TelephonyTest { @SmallTest public void testImsUpdateStatePendingHold() { mConnectionUT = new ImsPhoneConnection(mImsPhone, "+1 (700).555-41NN1234", mImsCT, - mForeGroundCall, false, false); + mForeGroundCall, false, false, new ImsDialArgs.Builder().build()); doReturn(true).when(mImsCall).isPendingHold(); assertFalse(mConnectionUT.update(mImsCall, Call.State.ACTIVE)); verify(mForeGroundCall, times(0)).update(eq(mConnectionUT), eq(mImsCall), @@ -240,7 +242,8 @@ public class ImsPhoneConnectionTest extends TelephonyTest { @SmallTest public void testPostDialWait() { mConnectionUT = new ImsPhoneConnection(mImsPhone, String.format("+1 (700).555-41NN%c1234", - PhoneNumberUtils.WAIT), mImsCT, mForeGroundCall, false, false); + PhoneNumberUtils.WAIT), mImsCT, mForeGroundCall, false, false, + new ImsDialArgs.Builder().build()); doReturn(Call.State.DIALING).when(mForeGroundCall).getState(); doAnswer(new Answer() { @Override @@ -263,7 +266,8 @@ public class ImsPhoneConnectionTest extends TelephonyTest { @MediumTest public void testPostDialPause() { mConnectionUT = new ImsPhoneConnection(mImsPhone, String.format("+1 (700).555-41NN%c1234", - PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false); + PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false, + new ImsDialArgs.Builder().build()); doReturn(Call.State.DIALING).when(mForeGroundCall).getState(); doAnswer(new Answer() { @Override @@ -385,7 +389,7 @@ public class ImsPhoneConnectionTest extends TelephonyTest { {"12345*00000", "12346", "12346"}}; for (String[] testAddress : testAddressMappingSet) { mConnectionUT = new ImsPhoneConnection(mImsPhone, testAddress[0], mImsCT, - mForeGroundCall, false, false); + mForeGroundCall, false, false, new ImsDialArgs.Builder().build()); mConnectionUT.setIsIncoming(true); mImsCallProfile.setCallExtra(ImsCallProfile.EXTRA_OI, testAddress[1]); mConnectionUT.updateAddressDisplay(mImsCall); @@ -403,7 +407,7 @@ public class ImsPhoneConnectionTest extends TelephonyTest { String updateAddress = "6789"; mConnectionUT = new ImsPhoneConnection(mImsPhone, inputAddress, mImsCT, mForeGroundCall, - false, false); + false, false, new ImsDialArgs.Builder().build()); mConnectionUT.setIsIncoming(false); mImsCallProfile.setCallExtra(ImsCallProfile.EXTRA_OI, updateAddress); mConnectionUT.updateAddressDisplay(mImsCall); diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java index 3cbf8bde6bf2b9124dcbca510c127a6213e2af44..d8173a22b1958f3a6f4befedbc8879312254e7a9 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java @@ -29,6 +29,7 @@ import android.os.AsyncResult; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; import android.telephony.ServiceState; +import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -126,6 +127,7 @@ public class ImsPhoneMmiCodeTest extends TelephonyTest { * Ensure that when an operation is not supported that the correct message is returned. */ @Test + @SmallTest public void testOperationNotSupported() { mImsPhoneMmiCode = ImsPhoneMmiCode.newNetworkInitiatedUssd(null, true, mImsPhoneUT); 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 3c4c65d5dbca6670e3f6aaa1490eb97b61ede343..b74c8af2dc2d26c5808fc4d39bbbdfb9e4386d4f 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java @@ -17,14 +17,23 @@ package com.android.internal.telephony.imsphone; import static android.Manifest.permission.MODIFY_PHONE_STATE; -import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS; import static android.telephony.CarrierConfigManager.USSD_OVER_CS_ONLY; import static android.telephony.CarrierConfigManager.USSD_OVER_CS_PREFERRED; import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_ONLY; import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_PREFERRED; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_3G; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR; import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE; import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL; +import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_SMS; +import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO; +import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -66,6 +75,7 @@ import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.RegistrationManager; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsUtImplBase; @@ -85,6 +95,7 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.domainselection.DomainSelectionResolver; import com.android.internal.telephony.gsm.SuppServiceNotification; import com.android.internal.telephony.imsphone.ImsPhone.SS; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; @@ -110,6 +121,7 @@ public class ImsPhoneTest extends TelephonyTest { private ImsPhoneCall mBackgroundCall; private ImsPhoneCall mRingingCall; private Handler mTestHandler; + private DomainSelectionResolver mDomainSelectionResolver; Connection mConnection; ImsUtInterface mImsUtInterface; @@ -135,6 +147,9 @@ public class ImsPhoneTest extends TelephonyTest { mTestHandler = mock(Handler.class); mConnection = mock(Connection.class); mImsUtInterface = mock(ImsUtInterface.class); + mDomainSelectionResolver = mock(DomainSelectionResolver.class); + doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported(); + DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver); mImsCT.mForegroundCall = mForegroundCall; mImsCT.mBackgroundCall = mBackgroundCall; @@ -178,6 +193,7 @@ public class ImsPhoneTest extends TelephonyTest { public void tearDown() throws Exception { mImsPhoneUT = null; mBundle = null; + DomainSelectionResolver.setDomainSelectionResolver(null); super.tearDown(); } @@ -629,6 +645,21 @@ public class ImsPhoneTest extends TelephonyTest { TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)); } + @Test + @SmallTest + public void testEcbmWhenDomainSelectionEnabled() { + DomainSelectionResolver dsResolver = mock(DomainSelectionResolver.class); + doReturn(true).when(dsResolver).isDomainSelectionSupported(); + DomainSelectionResolver.setDomainSelectionResolver(dsResolver); + + ImsEcbmStateListener imsEcbmStateListener = mImsPhoneUT.getImsEcbmStateListener(); + imsEcbmStateListener.onECBMEntered(); + imsEcbmStateListener.onECBMExited(); + + verify(mPhone, never()).setIsInEcm(anyBoolean()); + verify(mContext, never()).sendStickyBroadcastAsUser(any(), any()); + } + @Test @SmallTest public void testProcessDisconnectReason() throws Exception { @@ -736,7 +767,6 @@ public class ImsPhoneTest extends TelephonyTest { @Test @SmallTest public void testRoamingToAirplanModeIwlanInService() throws Exception { - doReturn(true).when(mAccessNetworksManager).isInLegacyMode(); doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState(); doReturn(true).when(mPhone).isRadioOn(); @@ -764,7 +794,6 @@ public class ImsPhoneTest extends TelephonyTest { @Test @SmallTest public void testRoamingToOutOfService() throws Exception { - doReturn(true).when(mAccessNetworksManager).isInLegacyMode(); doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState(); doReturn(true).when(mPhone).isRadioOn(); @@ -787,83 +816,6 @@ public class ImsPhoneTest extends TelephonyTest { verify(mImsManager, times(1)).setWfcMode(anyInt(), anyBoolean()); } - @Test - @SmallTest - public void testRoamingChangeForLteInLegacyMode() throws Exception { - doReturn(true).when(mAccessNetworksManager).isInLegacyMode(); - doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState(); - doReturn(true).when(mPhone).isRadioOn(); - - //roaming - data registration only on LTE - Message m = getServiceStateChangedMessage(getServiceStateDataOnly( - ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, true)); - // Inject the message synchronously instead of waiting for the thread to do it. - mImsPhoneUT.handleMessage(m); - m.recycle(); - - verify(mImsManager, times(1)).setWfcMode(anyInt(), eq(true)); - - // not roaming - data registration on LTE - m = getServiceStateChangedMessage(getServiceStateDataOnly( - ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, false)); - mImsPhoneUT.handleMessage(m); - m.recycle(); - - verify(mImsManager, times(1)).setWfcMode(anyInt(), eq(false)); - } - - @Test - @SmallTest - public void testDataOnlyRoamingCellToIWlanInLegacyMode() throws Exception { - doReturn(true).when(mAccessNetworksManager).isInLegacyMode(); - doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState(); - doReturn(true).when(mPhone).isRadioOn(); - - //roaming - data registration only on LTE - Message m = getServiceStateChangedMessage(getServiceStateDataOnly( - ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, true)); - // Inject the message synchronously instead of waiting for the thread to do it. - mImsPhoneUT.handleMessage(m); - m.recycle(); - - verify(mImsManager, times(1)).setWfcMode(anyInt(), eq(true)); - - // not roaming - data registration onto IWLAN - m = getServiceStateChangedMessage(getServiceStateDataOnly( - ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.STATE_IN_SERVICE, false)); - mImsPhoneUT.handleMessage(m); - m.recycle(); - - // Verify that it hasn't been called again. - verify(mImsManager, times(1)).setWfcMode(anyInt(), anyBoolean()); - } - - @Test - @SmallTest - public void testCellVoiceDataChangeToWlanInLegacyMode() throws Exception { - doReturn(true).when(mAccessNetworksManager).isInLegacyMode(); - doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState(); - doReturn(true).when(mPhone).isRadioOn(); - - //roaming - voice/data registration on LTE - ServiceState ss = getServiceStateDataAndVoice( - ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, true); - Message m = getServiceStateChangedMessage(ss); - // Inject the message synchronously instead of waiting for the thread to do it. - mImsPhoneUT.handleMessage(m); - - verify(mImsManager, times(1)).setWfcMode(anyInt(), eq(true)); - - // roaming - voice LTE, data registration onto IWLAN - modifyServiceStateData(ss, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, - ServiceState.STATE_IN_SERVICE, false); - mImsPhoneUT.handleMessage(m); - m.recycle(); - - // Verify that it hasn't been called again. - verify(mImsManager, times(1)).setWfcMode(anyInt(), anyBoolean()); - } - @Test public void testNonNullTrackersInImsPhone() throws Exception { assertNotNull(mImsPhoneUT.getEmergencyNumberTracker()); @@ -1018,7 +970,6 @@ public class ImsPhoneTest extends TelephonyTest { doReturn(subId).when(mPhone).getSubId(); SubscriptionInfo subInfo = mock(SubscriptionInfo.class); doReturn("gb").when(subInfo).getCountryIso(); - doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId); doReturn(new SubscriptionInfoInternal.Builder().setId(subId).setSimSlotIndex(0) .setCountryIso("gb").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(subId); @@ -1030,12 +981,7 @@ public class ImsPhoneTest extends TelephonyTest { }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539447777"); - } else { - verify(mSubscriptionController).setSubscriptionProperty( - subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539447777"); - } + verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539447777"); // 2. 1st invalid and 2nd valid: 2nd is set. associatedUris = new Uri[] { @@ -1044,12 +990,7 @@ public class ImsPhoneTest extends TelephonyTest { }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446666"); - } else { - verify(mSubscriptionController).setSubscriptionProperty( - subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446666"); - } + verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446666"); // 3. 1st sip-uri is not phone number and 2nd valid: 2nd is set. associatedUris = new Uri[] { @@ -1059,12 +1000,7 @@ public class ImsPhoneTest extends TelephonyTest { }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446677"); - } else { - verify(mSubscriptionController).setSubscriptionProperty( - subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446677"); - } + verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446677"); // Clean up mContextFixture.addCallingOrSelfPermission(""); @@ -1079,7 +1015,6 @@ public class ImsPhoneTest extends TelephonyTest { doReturn(subId).when(mPhone).getSubId(); SubscriptionInfo subInfo = mock(SubscriptionInfo.class); doReturn("gb").when(subInfo).getCountryIso(); - doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId); doReturn(new SubscriptionInfoInternal.Builder().setId(subId).setSimSlotIndex(0) .setCountryIso("gb").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(0); @@ -1091,49 +1026,367 @@ public class ImsPhoneTest extends TelephonyTest { }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); - } else { - verify(mSubscriptionController, never()).setSubscriptionProperty( - anyInt(), any(), any()); - } + verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); // 2. no URI; do not set associatedUris = new Uri[] {}; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); - } else { - verify(mSubscriptionController, never()).setSubscriptionProperty( - anyInt(), any(), any()); - } + verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); // 3. null URI; do not set associatedUris = new Uri[] { null }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); - } else { - verify(mSubscriptionController, never()).setSubscriptionProperty( - anyInt(), any(), any()); - } + verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); // 4. null pointer; do not set mImsPhoneUT.setPhoneNumberForSourceIms(null); - if (isSubscriptionManagerServiceEnabled()) { - verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); - } else { - verify(mSubscriptionController, never()).setSubscriptionProperty( - anyInt(), any(), any()); - } + 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. + */ + @Test + @SmallTest + public void testUpdateImsRegistrationInfoRadioTech() { + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + + int[] regInfo = mSimulatedCommands.getImsRegistrationInfo(); + assertNotNull(regInfo); + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + RegistrationManager.RegistrationCallback registrationCallback = + mImsPhoneUT.getImsMmTelRegistrationCallback(); + + ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder( + REGISTRATION_TECH_LTE).build(); + registrationCallback.onRegistered(attr); + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VOICE); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED + && regInfo[1] == REGISTRATION_TECH_LTE + && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // duplicated notification with the same radio technology + attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_LTE).build(); + registrationCallback.onRegistered(attr); + + // verify that there is no change in SimulatedCommands + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // radio technology changed + attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NR).build(); + registrationCallback.onRegistered(attr); + + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED + && regInfo[1] == REGISTRATION_TECH_NR + && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // duplicated notification with the same radio technology + attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NR).build(); + registrationCallback.onRegistered(attr); + + // verify that there is no change in SimulatedCommands + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // radio technology changed + attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_IWLAN).build(); + registrationCallback.onRegistered(attr); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED + && regInfo[1] == REGISTRATION_TECH_IWLAN + && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // duplicated notification with the same radio technology + attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_IWLAN).build(); + registrationCallback.onRegistered(attr); + + // verify that there is no change in SimulatedCommands + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // radio technology changed + attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_3G).build(); + registrationCallback.onRegistered(attr); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED + && regInfo[1] == REGISTRATION_TECH_3G + && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // duplicated notification with the same radio technology + attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_3G).build(); + registrationCallback.onRegistered(attr); + + // verify that there is no change in SimulatedCommands + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + } + + /** + * Verifies that valid capabilities is passed to RIL + * when IMS registration state changes to registered. + */ + @Test + @SmallTest + public void testUpdateImsRegistrationInfoCapabilities() { + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + + int[] regInfo = mSimulatedCommands.getImsRegistrationInfo(); + assertNotNull(regInfo); + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + RegistrationManager.RegistrationCallback registrationCallback = + mImsPhoneUT.getImsMmTelRegistrationCallback(); + + ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder( + REGISTRATION_TECH_LTE).build(); + registrationCallback.onRegistered(attr); + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VOICE); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED + && regInfo[1] == REGISTRATION_TECH_LTE + && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // duplicated notification with the same capability + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VOICE); + + // verify that there is no change in SimulatedCommands + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // capability changed + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VIDEO); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED + && regInfo[1] == REGISTRATION_TECH_LTE + && regInfo[3] == IMS_MMTEL_CAPABILITY_VIDEO); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // duplicated notification with the same capability + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VIDEO); + + // verify that there is no change in SimulatedCommands + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // capability changed + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_SMS); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED + && regInfo[1] == REGISTRATION_TECH_LTE + && regInfo[3] == IMS_MMTEL_CAPABILITY_SMS); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // duplicated notification with the same capability + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_SMS); + + // verify that there is no change in SimulatedCommands + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + + // capability changed, but no capability + mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_SMS); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + // verify that there is no change in SimulatedCommands. + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0); + } + + /** + * Verifies that valid state and reason is passed to RIL + * when IMS registration state changes to unregistered. + */ + @Test + @SmallTest + public void testUpdateImsRegistrationInfo() { + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + + int[] regInfo = mSimulatedCommands.getImsRegistrationInfo(); + assertNotNull(regInfo); + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0); + + RegistrationManager.RegistrationCallback registrationCallback = + mImsPhoneUT.getImsMmTelRegistrationCallback(); + + ImsReasonInfo reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, + ImsReasonInfo.CODE_UNSPECIFIED, ""); + + // unregistered with fatal error + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_NR); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED + && regInfo[1] == REGISTRATION_TECH_NR + && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0); + + // unregistered with fatal error but rat changed + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_LTE); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED + && regInfo[1] == REGISTRATION_TECH_LTE + && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0); + + // duplicated notification with the same suggested action + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_LTE); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + // verify that there is no update in the SimulatedCommands + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0); + + // unregistered with repeated error + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT, + REGISTRATION_TECH_LTE); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED + && regInfo[1] == REGISTRATION_TECH_LTE + && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT); + + // reset the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0); + + // duplicated notification with the same suggested action + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT, + REGISTRATION_TECH_LTE); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + // verify that there is no update in the SimulatedCommands + assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0); + + // unregistered with temporary error + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_NONE, REGISTRATION_TECH_LTE); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED + && regInfo[1] == REGISTRATION_TECH_LTE + && regInfo[2] == SUGGESTED_ACTION_NONE); + + // verifies that reason codes except ImsReasonInfo.CODE_REGISTRATION_ERROR are discarded. + reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE, + ImsReasonInfo.CODE_UNSPECIFIED, ""); + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_NR); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED + && regInfo[1] == REGISTRATION_TECH_NR + && regInfo[2] == SUGGESTED_ACTION_NONE); + + // change the registration info saved in the SimulatedCommands + mSimulatedCommands.updateImsRegistrationInfo(1, 1, 1, 1, null); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + assertTrue(regInfo[0] == 1 && regInfo[1] == 1 && regInfo[2] == 1); + + // duplicated notification with the same suggested action + registrationCallback.onUnregistered(reasonInfo, + SUGGESTED_ACTION_NONE, REGISTRATION_TECH_NR); + regInfo = mSimulatedCommands.getImsRegistrationInfo(); + + // verify that there is no update in the SimulatedCommands + assertTrue(regInfo[0] == 1 && regInfo[1] == 1 && regInfo[2] == 1); + } + + @Test + @SmallTest + public void testImsDialArgsBuilderFromForAlternateService() { + ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder() + .setIsEmergency(true) + .setEccCategory(2) + .build(); + + ImsPhone.ImsDialArgs copiedDialArgs = + ImsPhone.ImsDialArgs.Builder.from(dialArgs).build(); + + assertTrue(copiedDialArgs.isEmergency); + assertEquals(2, copiedDialArgs.eccCategory); + } + private ServiceState getServiceStateDataAndVoice(int rat, int regState, boolean isRoaming) { ServiceState ss = new ServiceState(); ss.setStateOutOfService(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java index cce064673b84695778b87908c235b6fd5be0af53..93fbfdc1948ec36a3dd6055f40bc46fb5d83ad99 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java @@ -16,10 +16,16 @@ package com.android.internal.telephony.imsphone; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE; +import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE; +import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -27,8 +33,10 @@ import static org.mockito.Mockito.verify; import android.net.Uri; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.RegistrationManager; import android.telephony.ims.RegistrationManager.RegistrationCallback; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.telephony.TelephonyTest; @@ -121,11 +129,13 @@ public class ImsRegistrationCallbackHelperTest extends TelephonyTest { // When onRegistered is called, the registration state should be // REGISTRATION_STATE_REGISTERED - callback.onRegistered(AccessNetworkType.IWLAN); + ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder( + ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).build(); + callback.onRegistered(attr); assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED, mRegistrationCallbackHelper.getImsRegistrationState()); - verify(mMockRegistrationUpdate).handleImsRegistered(anyInt()); + verify(mMockRegistrationUpdate).handleImsRegistered(attr); } @Test @@ -158,7 +168,25 @@ public class ImsRegistrationCallbackHelperTest extends TelephonyTest { // The registration state should be REGISTRATION_STATE_NOT_REGISTERED assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED, mRegistrationCallbackHelper.getImsRegistrationState()); - verify(mMockRegistrationUpdate).handleImsUnregistered(reasonInfo); + verify(mMockRegistrationUpdate).handleImsUnregistered(eq(reasonInfo), + eq(SUGGESTED_ACTION_NONE), eq(REGISTRATION_TECH_NONE)); + } + + @Test + @SmallTest + public void testImsUnRegisteredWithSuggestedAction() { + // Verify the RegistrationCallback should not be null + RegistrationCallback callback = mRegistrationCallbackHelper.getCallback(); + assertNotNull(callback); + + ImsReasonInfo reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 0); + callback.onUnregistered(reasonInfo, SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, + REGISTRATION_TECH_LTE); + + assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED, + mRegistrationCallbackHelper.getImsRegistrationState()); + verify(mMockRegistrationUpdate).handleImsUnregistered(eq(reasonInfo), + eq(SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK), eq(REGISTRATION_TECH_LTE)); } @Test diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java index a4e257476e49e6473acfbc1520a5bab06ac52b58..d4e1b86cda186e35c27eefe11561e48cabf48093 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.metrics; import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH; import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE; +import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SHORT_CODE_SMS; import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE; import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY; import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE; @@ -43,6 +44,7 @@ import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; +import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms; import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage; import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession; import com.android.internal.telephony.uicc.IccCardStatus.CardState; @@ -102,7 +104,7 @@ public class MetricsCollectorTest extends TelephonyTest { mActivePort = mock(UiccPort.class); mServiceStateStats = mock(ServiceStateStats.class); mMetricsCollector = - new MetricsCollector(mContext, mPersistAtomsStorage); + new MetricsCollector(mContext, mPersistAtomsStorage, mDeviceStateHelper); doReturn(mSST).when(mSecondPhone).getServiceStateTracker(); doReturn(mServiceStateStats).when(mSST).getServiceStateStats(); } @@ -412,4 +414,45 @@ public class MetricsCollectorTest extends TelephonyTest { assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS); // TODO(b/153196254): verify atom contents } + + @Test + public void onPullAtom_outgoingShortCodeSms_empty() { + doReturn(new OutgoingShortCodeSms[0]).when(mPersistAtomsStorage) + .getOutgoingShortCodeSms(anyLong()); + List actualAtoms = new ArrayList<>(); + + int result = mMetricsCollector.onPullAtom(OUTGOING_SHORT_CODE_SMS, actualAtoms); + + assertThat(actualAtoms).hasSize(0); + assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS); + } + + @Test + public void onPullAtom_outgoingShortCodeSms_tooFrequent() { + doReturn(null).when(mPersistAtomsStorage).getOutgoingShortCodeSms(anyLong()); + List actualAtoms = new ArrayList<>(); + + int result = mMetricsCollector.onPullAtom(OUTGOING_SHORT_CODE_SMS, actualAtoms); + + assertThat(actualAtoms).hasSize(0); + assertThat(result).isEqualTo(StatsManager.PULL_SKIP); + verify(mPersistAtomsStorage, times(1)) + .getOutgoingShortCodeSms(eq(MIN_COOLDOWN_MILLIS)); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void onPullAtom_outgoingShortCodeSms_multipleSms() { + OutgoingShortCodeSms outgoingShortCodeSms = new OutgoingShortCodeSms(); + doReturn(new OutgoingShortCodeSms[] {outgoingShortCodeSms, outgoingShortCodeSms, + outgoingShortCodeSms, outgoingShortCodeSms}) + .when(mPersistAtomsStorage) + .getOutgoingShortCodeSms(anyLong()); + List actualAtoms = new ArrayList<>(); + + int result = mMetricsCollector.onPullAtom(OUTGOING_SHORT_CODE_SMS, actualAtoms); + + assertThat(actualAtoms).hasSize(4); + assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java index 10151b8b0b2d6e091e90948777bd0d14b2e69630..dc9be1c579f97238df194df36197be86cc9f97a7 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java @@ -43,7 +43,6 @@ import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.telephony.IccCard; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; @@ -83,27 +82,17 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(0).when(mPhone).getPhoneId(); doReturn(1).when(mPhone).getSubId(); doReturn(100).when(mPhone).getCarrierId(); - doReturn("6506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); doReturn("6506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); - doReturn("") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); doReturn("") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); - doReturn("+16506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); doReturn("+16506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); SubscriptionInfo subscriptionInfo1 = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo1).getCountryIso(); - doReturn(subscriptionInfo1).when(mSubscriptionController).getSubscriptionInfo(1); doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0) .setCountryIso("us").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(1); @@ -133,32 +122,23 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(UiccSlot.VOLTAGE_CLASS_A).when(uiccSlot1).getMinimumVoltageClass(); doReturn(uiccSlot1).when(mUiccController).getUiccSlotForPhone(0); doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100); + doReturn(false).when(mTelephonyManager).isVoNrEnabled(); // phone 1 setup doReturn(mContext).when(mSecondPhone).getContext(); doReturn(1).when(mSecondPhone).getPhoneId(); doReturn(2).when(mSecondPhone).getSubId(); doReturn(101).when(mSecondPhone).getCarrierId(); - doReturn("0123") - .when(mSubscriptionController) - .getPhoneNumber(2, PHONE_NUMBER_SOURCE_UICC, null, null); doReturn("0123") .when(mSubscriptionManagerService) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_UICC, null, null); - doReturn("16506950123") - .when(mSubscriptionController) - .getPhoneNumber(2, PHONE_NUMBER_SOURCE_CARRIER, null, null); doReturn("16506950123") .when(mSubscriptionManagerService) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_CARRIER, null, null); - doReturn("+16506950123") - .when(mSubscriptionController) - .getPhoneNumber(2, PHONE_NUMBER_SOURCE_IMS, null, null); doReturn("+16506950123") .when(mSubscriptionManagerService) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_IMS, null, null); SubscriptionInfo subscriptionInfo2 = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo2).getCountryIso(); - doReturn(subscriptionInfo2).when(mSubscriptionController).getSubscriptionInfo(2); doReturn(new SubscriptionInfoInternal.Builder().setId(2).setSimSlotIndex(1) .setCountryIso("us").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(2); @@ -210,6 +190,7 @@ public class PerSimStatusTest extends TelephonyTest { PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_A, perSimStatus1.minimumVoltageClass); assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus1.unmeteredNetworks); + assertEquals(false, perSimStatus1.vonrEnabled); assertEquals(101, perSimStatus2.carrierId); assertEquals(1, perSimStatus2.phoneNumberSourceUicc); assertEquals(2, perSimStatus2.phoneNumberSourceCarrier); @@ -228,12 +209,12 @@ public class PerSimStatusTest extends TelephonyTest { PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_B, perSimStatus2.minimumVoltageClass); assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus2.unmeteredNetworks); + assertEquals(false, perSimStatus2.vonrEnabled); } @Test @SmallTest - public void onPullAtom_perSimStatus_noSubscriptionController() throws Exception { - replaceInstance(SubscriptionController.class, "sInstance", null, null); + public void onPullAtom_perSimStatus_noSubscriptionManagerService() throws Exception { replaceInstance(SubscriptionManagerService.class, "sInstance", null, null); PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone); @@ -247,27 +228,17 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(0).when(mPhone).getPhoneId(); doReturn(1).when(mPhone).getSubId(); doReturn(100).when(mPhone).getCarrierId(); - doReturn("6506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); doReturn("6506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); - doReturn("") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); doReturn("") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); - doReturn("+16506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); doReturn("+16506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo).getCountryIso(); - doReturn(subscriptionInfo).when(mSubscriptionController).getSubscriptionInfo(1); doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0) .setCountryIso("us").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(1); @@ -286,6 +257,7 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(UiccSlot.VOLTAGE_CLASS_A).when(uiccSlot1).getMinimumVoltageClass(); doReturn(uiccSlot1).when(mUiccController).getUiccSlotForPhone(0); doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100); + doReturn(true).when(mTelephonyManager).isVoNrEnabled(); PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone); @@ -306,6 +278,7 @@ public class PerSimStatusTest extends TelephonyTest { PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_A, perSimStatus.minimumVoltageClass); assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus.unmeteredNetworks); + assertEquals(true, perSimStatus.vonrEnabled); } @Test @@ -314,27 +287,17 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(0).when(mPhone).getPhoneId(); doReturn(1).when(mPhone).getSubId(); doReturn(100).when(mPhone).getCarrierId(); - doReturn("6506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); doReturn("6506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); - doReturn("") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); doReturn("") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); - doReturn("+16506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); doReturn("+16506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo).getCountryIso(); - doReturn(subscriptionInfo).when(mSubscriptionController).getSubscriptionInfo(1); doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0) .setCountryIso("us").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(1); @@ -355,6 +318,7 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(UiccSlot.VOLTAGE_CLASS_A).when(uiccSlot1).getMinimumVoltageClass(); doReturn(uiccSlot1).when(mUiccController).getUiccSlotForPhone(0); doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100); + doReturn(true).when(mTelephonyManager).isVoNrEnabled(); PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone); @@ -375,6 +339,7 @@ public class PerSimStatusTest extends TelephonyTest { PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_A, perSimStatus.minimumVoltageClass); assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus.unmeteredNetworks); + assertEquals(true, perSimStatus.vonrEnabled); } @Test @@ -383,27 +348,17 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(0).when(mPhone).getPhoneId(); doReturn(1).when(mPhone).getSubId(); doReturn(100).when(mPhone).getCarrierId(); - doReturn("6506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); doReturn("6506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null); - doReturn("") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); doReturn("") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null); - doReturn("+16506953210") - .when(mSubscriptionController) - .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); doReturn("+16506953210") .when(mSubscriptionManagerService) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null); SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo).getCountryIso(); - doReturn(subscriptionInfo).when(mSubscriptionController).getSubscriptionInfo(1); doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0) .setCountryIso("us").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(1); @@ -420,6 +375,7 @@ public class PerSimStatusTest extends TelephonyTest { doReturn(iccCard).when(mPhone).getIccCard(); doReturn(null).when(mUiccController).getUiccSlotForPhone(0); doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100); + doReturn(true).when(mTelephonyManager).isVoNrEnabled(); PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone); @@ -440,5 +396,6 @@ public class PerSimStatusTest extends TelephonyTest { PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_UNKNOWN, perSimStatus.minimumVoltageClass); assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus.unmeteredNetworks); + assertEquals(true, perSimStatus.vonrEnabled); } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java index e46b822bd1fdad17dfd1d6898da70497b3f41efc..3307813d74be82f70f37ed278bad77de4fea18c9 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java @@ -49,6 +49,7 @@ import android.annotation.Nullable; import android.content.Context; import android.os.Build; import android.telephony.DisconnectCause; +import android.telephony.SatelliteProtoEnums; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.telephony.TelephonyProtoEnums; @@ -68,10 +69,17 @@ import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationFeat import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats; import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats; import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination; +import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms; import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms; import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent; import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats; import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender; import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats; import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse; import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats; @@ -140,7 +148,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { private CellularDataServiceSwitch mServiceSwitch1Proto; private CellularDataServiceSwitch mServiceSwitch2Proto; - // service states for slot 0 and 1 + // Service states for slot 0 and 1 private CellularServiceState mServiceState1Proto; private CellularServiceState mServiceState2Proto; private CellularServiceState mServiceState3Proto; @@ -224,6 +232,34 @@ public class PersistAtomsStorageTest extends TelephonyTest { private SipTransportSession mSipTransportSession2; private SipTransportSession[] mSipTransportSession; + private OutgoingShortCodeSms mOutgoingShortCodeSms1; + private OutgoingShortCodeSms mOutgoingShortCodeSms2; + private OutgoingShortCodeSms[] mOutgoingShortCodeSms; + + private SatelliteController mSatelliteController1; + private SatelliteController mSatelliteController2; + private SatelliteController[] mSatelliteControllers; + + private SatelliteSession mSatelliteSession1; + private SatelliteSession mSatelliteSession2; + private SatelliteSession[] mSatelliteSessions; + + private SatelliteIncomingDatagram mSatelliteIncomingDatagram1; + private SatelliteIncomingDatagram mSatelliteIncomingDatagram2; + private SatelliteIncomingDatagram[] mSatelliteIncomingDatagrams; + + private SatelliteOutgoingDatagram mSatelliteOutgoingDatagram1; + private SatelliteOutgoingDatagram mSatelliteOutgoingDatagram2; + private SatelliteOutgoingDatagram[] mSatelliteOutgoingDatagrams; + + private SatelliteProvision mSatelliteProvision1; + private SatelliteProvision mSatelliteProvision2; + private SatelliteProvision[] mSatelliteProvisions; + + private SatelliteSosMessageRecommender mSatelliteSosMessageRecommender1; + private SatelliteSosMessageRecommender mSatelliteSosMessageRecommender2; + private SatelliteSosMessageRecommender[] mSatelliteSosMessageRecommenders; + private void makeTestData() { // MO call with SRVCC (LTE to UMTS) mCall1Proto = new VoiceCallSession(); @@ -859,6 +895,158 @@ public class PersistAtomsStorageTest extends TelephonyTest { mSipTransportSession = new SipTransportSession[] {mSipTransportSession1, mSipTransportSession2}; + + mOutgoingShortCodeSms1 = new OutgoingShortCodeSms(); + mOutgoingShortCodeSms1.category = 1; + mOutgoingShortCodeSms1.xmlVersion = 30; + mOutgoingShortCodeSms1.shortCodeSmsCount = 1; + + mOutgoingShortCodeSms2 = new OutgoingShortCodeSms(); + mOutgoingShortCodeSms2.category = 2; + mOutgoingShortCodeSms2.xmlVersion = 31; + mOutgoingShortCodeSms2.shortCodeSmsCount = 1; + + mOutgoingShortCodeSms = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1, + mOutgoingShortCodeSms2}; + + generateTestSatelliteData(); + } + + private void generateTestSatelliteData() { + mSatelliteController1 = new SatelliteController(); + mSatelliteController1.countOfSatelliteServiceEnablementsSuccess = 2; + mSatelliteController1.countOfSatelliteServiceEnablementsFail = 0; + mSatelliteController1.countOfOutgoingDatagramSuccess = 8; + mSatelliteController1.countOfOutgoingDatagramFail = 9; + mSatelliteController1.countOfIncomingDatagramSuccess = 10; + mSatelliteController1.countOfIncomingDatagramFail = 11; + mSatelliteController1.countOfDatagramTypeSosSmsSuccess = 5; + mSatelliteController1.countOfDatagramTypeSosSmsFail = 5; + mSatelliteController1.countOfDatagramTypeLocationSharingSuccess = 6; + mSatelliteController1.countOfDatagramTypeLocationSharingFail = 6; + mSatelliteController1.countOfProvisionSuccess = 3; + mSatelliteController1.countOfProvisionFail = 4; + mSatelliteController1.countOfDeprovisionSuccess = 5; + mSatelliteController1.countOfDeprovisionFail = 6; + mSatelliteController1.totalServiceUptimeSec = 60 * 60 * 24 * 7; + mSatelliteController1.totalBatteryConsumptionPercent = 7; + mSatelliteController1.totalBatteryChargedTimeSec = 60 * 60 * 3 * 1; + + mSatelliteController2 = new SatelliteController(); + mSatelliteController2.countOfSatelliteServiceEnablementsSuccess = 2 + 1; + mSatelliteController2.countOfSatelliteServiceEnablementsFail = 0 + 1; + mSatelliteController2.countOfOutgoingDatagramSuccess = 8 + 1; + mSatelliteController2.countOfOutgoingDatagramFail = 9 + 1; + mSatelliteController2.countOfIncomingDatagramSuccess = 10 + 1; + mSatelliteController2.countOfIncomingDatagramFail = 11 + 1; + mSatelliteController2.countOfDatagramTypeSosSmsSuccess = 5 + 1; + mSatelliteController2.countOfDatagramTypeSosSmsFail = 5 + 1; + mSatelliteController2.countOfDatagramTypeLocationSharingSuccess = 6 + 1; + mSatelliteController2.countOfDatagramTypeLocationSharingFail = 6 + 1; + mSatelliteController2.countOfProvisionSuccess = 13; + mSatelliteController2.countOfProvisionFail = 14; + mSatelliteController2.countOfDeprovisionSuccess = 15; + mSatelliteController2.countOfDeprovisionFail = 16; + mSatelliteController2.totalServiceUptimeSec = 60 * 60 * 12; + mSatelliteController2.totalBatteryConsumptionPercent = 14; + mSatelliteController1.totalBatteryChargedTimeSec = 60 * 60 * 3; + + // SatelliteController atom has one data point + mSatelliteControllers = + new SatelliteController[] { + mSatelliteController1 + }; + + mSatelliteSession1 = new SatelliteSession(); + mSatelliteSession1.satelliteServiceInitializationResult = + SatelliteProtoEnums.SATELLITE_ERROR_NONE; + mSatelliteSession1.satelliteTechnology = + SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY; + mSatelliteSession1.count = 1; + + mSatelliteSession2 = new SatelliteSession(); + mSatelliteSession2.satelliteServiceInitializationResult = + SatelliteProtoEnums.SATELLITE_MODEM_ERROR; + mSatelliteSession2.satelliteTechnology = + SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_NB_IOT_NTN; + mSatelliteSession2.count = 1; + + mSatelliteSessions = + new SatelliteSession[] { + mSatelliteSession1, mSatelliteSession2 + }; + + mSatelliteIncomingDatagram1 = new SatelliteIncomingDatagram(); + mSatelliteIncomingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE; + mSatelliteIncomingDatagram1.datagramSizeBytes = 1 * 1024; + mSatelliteIncomingDatagram1.datagramTransferTimeMillis = 3 * 1000; + + mSatelliteIncomingDatagram2 = new SatelliteIncomingDatagram(); + mSatelliteIncomingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR; + mSatelliteIncomingDatagram2.datagramSizeBytes = 512; + mSatelliteIncomingDatagram2.datagramTransferTimeMillis = 1 * 1000; + + mSatelliteIncomingDatagrams = + new SatelliteIncomingDatagram[] { + mSatelliteIncomingDatagram1, mSatelliteIncomingDatagram2 + }; + + mSatelliteOutgoingDatagram1 = new SatelliteOutgoingDatagram(); + mSatelliteOutgoingDatagram1.datagramType = + SatelliteProtoEnums.DATAGRAM_TYPE_LOCATION_SHARING; + mSatelliteOutgoingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE; + mSatelliteOutgoingDatagram1.datagramSizeBytes = 1 * 1024; + mSatelliteOutgoingDatagram1.datagramTransferTimeMillis = 3 * 1000; + + mSatelliteOutgoingDatagram2 = new SatelliteOutgoingDatagram(); + mSatelliteOutgoingDatagram2.datagramType = + SatelliteProtoEnums.DATAGRAM_TYPE_SOS_MESSAGE; + mSatelliteOutgoingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR; + mSatelliteOutgoingDatagram2.datagramSizeBytes = 512; + mSatelliteOutgoingDatagram2.datagramTransferTimeMillis = 1 * 1000; + + mSatelliteOutgoingDatagrams = + new SatelliteOutgoingDatagram[] { + mSatelliteOutgoingDatagram1, mSatelliteOutgoingDatagram2 + }; + + mSatelliteProvision1 = new SatelliteProvision(); + mSatelliteProvision1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE; + mSatelliteProvision1.provisioningTimeSec = 3 * 60; + mSatelliteProvision1.isProvisionRequest = true; + mSatelliteProvision1.isCanceled = false; + + mSatelliteProvision2 = new SatelliteProvision(); + mSatelliteProvision2.resultCode = SatelliteProtoEnums.SATELLITE_SERVICE_NOT_PROVISIONED; + mSatelliteProvision2.provisioningTimeSec = 0; + mSatelliteProvision2.isProvisionRequest = false; + mSatelliteProvision2.isCanceled = true; + + mSatelliteProvisions = + new SatelliteProvision[] { + mSatelliteProvision1, mSatelliteProvision2 + }; + + mSatelliteSosMessageRecommender1 = new SatelliteSosMessageRecommender(); + mSatelliteSosMessageRecommender1.isDisplaySosMessageSent = true; + mSatelliteSosMessageRecommender1.countOfTimerStarted = 5; + mSatelliteSosMessageRecommender1.isImsRegistered = false; + mSatelliteSosMessageRecommender1.cellularServiceState = + TelephonyProtoEnums.SERVICE_STATE_OUT_OF_SERVICE; + mSatelliteSosMessageRecommender1.count = 1; + + mSatelliteSosMessageRecommender2 = new SatelliteSosMessageRecommender(); + mSatelliteSosMessageRecommender2.isDisplaySosMessageSent = false; + mSatelliteSosMessageRecommender2.countOfTimerStarted = 3; + mSatelliteSosMessageRecommender2.isImsRegistered = true; + mSatelliteSosMessageRecommender2.cellularServiceState = + TelephonyProtoEnums.SERVICE_STATE_POWER_OFF; + mSatelliteSosMessageRecommender2.count = 1; + + mSatelliteSosMessageRecommenders = + new SatelliteSosMessageRecommender[] { + mSatelliteSosMessageRecommender1, mSatelliteSosMessageRecommender2 + }; } private static class TestablePersistAtomsStorage extends PersistAtomsStorage { @@ -997,6 +1185,27 @@ public class PersistAtomsStorageTest extends TelephonyTest { mSipTransportSession1 = null; mSipTransportSession2 = null; mSipTransportSession = null; + mOutgoingShortCodeSms1 = null; + mOutgoingShortCodeSms2 = null; + mOutgoingShortCodeSms = null; + mSatelliteController1 = null; + mSatelliteController2 = null; + mSatelliteControllers = null; + mSatelliteSession1 = null; + mSatelliteSession2 = null; + mSatelliteSessions = null; + mSatelliteIncomingDatagram1 = null; + mSatelliteIncomingDatagram2 = null; + mSatelliteIncomingDatagrams = null; + mSatelliteOutgoingDatagram1 = null; + mSatelliteOutgoingDatagram2 = null; + mSatelliteOutgoingDatagrams = null; + mSatelliteProvision1 = null; + mSatelliteProvision2 = null; + mSatelliteProvisions = null; + mSatelliteSosMessageRecommender1 = null; + mSatelliteSosMessageRecommender2 = null; + mSatelliteSosMessageRecommenders = null; super.tearDown(); } @@ -1226,7 +1435,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum VoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(100L); - // should be denied + // Should be denied assertNull(voiceCallRatUsage); } @@ -1244,7 +1453,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis; VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved, other fields should be unaffected assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage1); assertProtoArrayIsEmpty(voiceCallRatUsage2); @@ -1275,7 +1484,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(100L); - // should be denied + // Should be denied assertNull(voiceCallSession); } @@ -1293,7 +1502,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.getAtomsProto().voiceCallRatUsagePullTimestampMillis; VoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved, other fields should be unaffected assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession1); assertProtoArrayIsEmpty(voiceCallSession2); @@ -1325,7 +1534,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mServiceState1Proto, mServiceSwitch1Proto); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); CellularServiceState[] serviceStates = mPersistAtomsStorage.getCellularServiceStates(0L); CellularDataServiceSwitch[] serviceSwitches = @@ -1348,7 +1557,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mServiceState2Proto, mServiceSwitch2Proto); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); CellularServiceState[] serviceStates = mPersistAtomsStorage.getCellularServiceStates(0L); CellularDataServiceSwitch[] serviceSwitches = @@ -1442,7 +1651,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { CellularDataServiceSwitch[] serviceSwitches = mPersistAtomsStorage.getCellularDataServiceSwitches(100L); - // should be denied + // Should be denied assertNull(serviceSwitches); } @@ -1459,7 +1668,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { CellularDataServiceSwitch[] serviceSwitches2 = mPersistAtomsStorage.getCellularDataServiceSwitches(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new CellularDataServiceSwitch[] {mServiceSwitch1Proto, mServiceSwitch2Proto}, @@ -1487,7 +1696,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum CellularServiceState[] serviceStates = mPersistAtomsStorage.getCellularServiceStates(100L); - // should be denied + // Should be denied assertNull(serviceStates); } @@ -1502,7 +1711,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(100L); CellularServiceState[] serviceStates2 = mPersistAtomsStorage.getCellularServiceStates(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new CellularServiceState[] { @@ -1536,7 +1745,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0)); mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L); assertProtoArrayEquals(new ImsRegistrationStats[] {mImsRegistrationStatsLte0}, regStats); @@ -1552,7 +1761,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsWifi0)); mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L); assertProtoArrayEqualsIgnoringOrder( @@ -1624,7 +1833,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsRegistrationTermination(mImsRegistrationTerminationLte); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsRegistrationTermination[] terminations = mPersistAtomsStorage.getImsRegistrationTerminations(0L); @@ -1642,7 +1851,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsRegistrationTermination(mImsRegistrationTerminationWifi); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsRegistrationTermination[] terminations = mPersistAtomsStorage.getImsRegistrationTerminations(0L); @@ -1707,7 +1916,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum ImsRegistrationStats[] stats = mPersistAtomsStorage.getImsRegistrationStats(100L); - // should be denied + // Should be denied assertNull(stats); } @@ -1722,7 +1931,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(100L); ImsRegistrationStats[] stats2 = mPersistAtomsStorage.getImsRegistrationStats(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new ImsRegistrationStats[] { @@ -1753,7 +1962,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { ImsRegistrationTermination[] terminations = mPersistAtomsStorage.getImsRegistrationTerminations(100L); - // should be denied + // Should be denied assertNull(terminations); } @@ -1770,7 +1979,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { ImsRegistrationTermination[] terminations2 = mPersistAtomsStorage.getImsRegistrationTerminations(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new ImsRegistrationTermination[] { @@ -1935,7 +2144,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { ImsRegistrationFeatureTagStats[] result = mPersistAtomsStorage.getImsRegistrationFeatureTagStats(100L); - // should be denied + // Should be denied assertNull(result); } @@ -1952,7 +2161,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { ImsRegistrationFeatureTagStats[] statses2 = mPersistAtomsStorage.getImsRegistrationFeatureTagStats(50L); - // first results of get should have two atoms, second should be empty + // First results of get should have two atoms, second should be empty // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder(mImsRegistrationFeatureTagStatses, statses1); assertProtoArrayEquals(new ImsRegistrationFeatureTagStats[0], statses2); @@ -2002,13 +2211,13 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); - // store 11 same atoms, but only 1 atoms stored with count 11 + // Store 11 same atoms, but only 1 atoms stored with count 11 for (int i = 0; i < 11; i++) { mPersistAtomsStorage .addRcsClientProvisioningStats(mRcsClientProvisioningStats1Proto); mPersistAtomsStorage.incTimeMillis(100L); } - // store 1 different atom and count 1 + // Store 1 different atom and count 1 mPersistAtomsStorage .addRcsClientProvisioningStats(mRcsClientProvisioningStats2Proto); @@ -2017,7 +2226,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { RcsClientProvisioningStats[] result = mPersistAtomsStorage.getRcsClientProvisioningStats(0L); - // first atom has count 11, the other has 1 + // First atom has count 11, the other has 1 assertHasStatsAndCount(result, mRcsClientProvisioningStats1Proto, 11); assertHasStatsAndCount(result, mRcsClientProvisioningStats2Proto, 1); } @@ -2032,7 +2241,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { RcsClientProvisioningStats[] result = mPersistAtomsStorage.getRcsClientProvisioningStats(100L); - // should be denied + // Should be denied assertNull(result); } @@ -2049,7 +2258,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { RcsClientProvisioningStats[] statses2 = mPersistAtomsStorage.getRcsClientProvisioningStats(50L); - // first results of get should have two atoms, second should be empty + // First results of get should have two atoms, second should be empty // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder(mRcsClientProvisioningStatses, statses1); assertProtoArrayEquals(new RcsClientProvisioningStats[0], statses2); @@ -2101,8 +2310,8 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); - // store 5 same atoms (1Proto), but only 1 atoms stored with count 5, total time 2000L * 5 - // store 5 same atoms (2Proto), but only 1 atoms stored with count 5, total time 2000L * 5 + // Store 5 same atoms (1Proto), but only 1 atoms stored with count 5, total time 2000L * 5 + // Store 5 same atoms (2Proto), but only 1 atoms stored with count 5, total time 2000L * 5 for (int i = 0; i < maxCount; i++) { mPersistAtomsStorage @@ -2140,7 +2349,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { RcsAcsProvisioningStats[] result = mPersistAtomsStorage.getRcsAcsProvisioningStats(100L); - // should be denied + // Should be denied assertNull(result); } @@ -2157,7 +2366,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { RcsAcsProvisioningStats[] statses2 = mPersistAtomsStorage.getRcsAcsProvisioningStats(DAY_IN_MILLIS - HOUR_IN_MILLIS); - // first results of get should have two atoms, second should be empty + // First results of get should have two atoms, second should be empty // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder(mRcsAcsProvisioningStatses, statses1); assertProtoArrayEquals(new RcsAcsProvisioningStats[0], statses2); @@ -2183,7 +2392,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsRegistrationServiceDescStats(mImsRegistrationServiceIm); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsRegistrationServiceDescStats[] outputs = mPersistAtomsStorage.getImsRegistrationServiceDescStats(0L); @@ -2201,7 +2410,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsRegistrationServiceDescStats(mImsRegistrationServiceFt); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsRegistrationServiceDescStats[] output = mPersistAtomsStorage.getImsRegistrationServiceDescStats(0L); @@ -2248,7 +2457,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { ImsRegistrationServiceDescStats[] output = mPersistAtomsStorage.getImsRegistrationServiceDescStats(100L); - // should be denied + // Should be denied assertNull(output); } @@ -2265,7 +2474,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { ImsRegistrationServiceDescStats[] output2 = mPersistAtomsStorage.getImsRegistrationServiceDescStats(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new ImsRegistrationServiceDescStats[] { @@ -2436,7 +2645,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsDedicatedBearerListenerEvent(mImsDedicatedBearerListenerEvent1); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsDedicatedBearerListenerEvent[] outputs = mPersistAtomsStorage.getImsDedicatedBearerListenerEvent(0L); @@ -2454,7 +2663,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsDedicatedBearerListenerEvent(mImsDedicatedBearerListenerEvent2); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsDedicatedBearerListenerEvent[] output = mPersistAtomsStorage.getImsDedicatedBearerListenerEvent(0L); @@ -2495,7 +2704,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsDedicatedBearerEvent(mImsDedicatedBearerEvent1); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsDedicatedBearerEvent[] outputs = mPersistAtomsStorage.getImsDedicatedBearerEvent(0L); @@ -2513,7 +2722,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addImsDedicatedBearerEvent(mImsDedicatedBearerEvent2); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); ImsDedicatedBearerEvent[] output = mPersistAtomsStorage.getImsDedicatedBearerEvent(0L); @@ -2588,7 +2797,6 @@ public class PersistAtomsStorageTest extends TelephonyTest { mImsDedicatedBearerEvent2, newStats}, stats); } - @Test @SmallTest public void addUceEventStats_emptyProto() throws Exception { @@ -2597,7 +2805,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addUceEventStats(mUceEventStats1); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); UceEventStats[] outputs = mPersistAtomsStorage.getUceEventStats(0L); assertProtoArrayEquals( @@ -2614,7 +2822,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addUceEventStats(mUceEventStats2); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); UceEventStats[] output = mPersistAtomsStorage.getUceEventStats(0L); assertProtoArrayEqualsIgnoringOrder( @@ -2688,7 +2896,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.addPresenceNotifyEvent(mPresenceNotifyEvent2); mPersistAtomsStorage.incTimeMillis(100L); - // service state and service switch should be added successfully + // Service state and service switch should be added successfully verifyCurrentStateSavedToFileOnce(); PresenceNotifyEvent[] output = mPersistAtomsStorage.getPresenceNotifyEvent(0L); assertProtoArrayEqualsIgnoringOrder( @@ -2704,7 +2912,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum PresenceNotifyEvent[] output = mPersistAtomsStorage.getPresenceNotifyEvent(100L); - // should be denied + // Should be denied assertNull(output); } @@ -2719,7 +2927,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(100L); PresenceNotifyEvent[] output2 = mPersistAtomsStorage.getPresenceNotifyEvent(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new PresenceNotifyEvent[] {mPresenceNotifyEvent1, mPresenceNotifyEvent2}, output1); @@ -2846,7 +3054,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { SipTransportFeatureTagStats[] outputs = mPersistAtomsStorage.getSipTransportFeatureTagStats(100L); - // should be denied + // Should be denied assertNull(outputs); } @@ -2865,7 +3073,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { SipTransportFeatureTagStats[] output2 = mPersistAtomsStorage.getSipTransportFeatureTagStats(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new SipTransportFeatureTagStats[] { @@ -2988,7 +3196,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum SipDelegateStats[] outputs = mPersistAtomsStorage.getSipDelegateStats(100L); - // should be denied + // Should be denied assertNull(outputs); } @@ -3005,7 +3213,7 @@ public class PersistAtomsStorageTest extends TelephonyTest { mPersistAtomsStorage.incTimeMillis(100L); SipDelegateStats[] output2 = mPersistAtomsStorage.getSipDelegateStats(50L); - // first set of results should equal to file contents, second should be empty, corresponding + // First set of results should equal to file contents, second should be empty, corresponding // pull timestamp should be updated and saved assertProtoArrayEqualsIgnoringOrder( new SipDelegateStats[] { @@ -3131,18 +3339,18 @@ public class PersistAtomsStorageTest extends TelephonyTest { createEmptyTestFile(); mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); - // store 11 same atoms, but only 1 atoms stored with count 11 + // Store 11 same atoms, but only 1 atoms stored with count 11 for (int i = 0; i < 11; i++) { mPersistAtomsStorage.addSipMessageResponse(mSipMessageResponse1); mPersistAtomsStorage.incTimeMillis(100L); } - // store 1 different atom and count 1 + // Store 1 different atom and count 1 mPersistAtomsStorage.addSipMessageResponse(mSipMessageResponse2); verifyCurrentStateSavedToFileOnce(); SipMessageResponse[] result = mPersistAtomsStorage.getSipMessageResponse(0L); - // first atom has count 11, the other has 1 + // First atom has count 11, the other has 1 assertHasStats(result, mSipMessageResponse1, 11); assertHasStats(result, mSipMessageResponse2, 1); } @@ -3200,18 +3408,18 @@ public class PersistAtomsStorageTest extends TelephonyTest { createEmptyTestFile(); mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); - // store 11 same atoms, but only 1 atoms stored with count 11 + // Store 11 same atoms, but only 1 atoms stored with count 11 for (int i = 0; i < 11; i++) { mPersistAtomsStorage.addCompleteSipTransportSession(mSipTransportSession1); mPersistAtomsStorage.incTimeMillis(100L); } - // store 1 different atom and count 1 + // Store 1 different atom and count 1 mPersistAtomsStorage.addCompleteSipTransportSession(mSipTransportSession2); verifyCurrentStateSavedToFileOnce(); SipTransportSession[] result = mPersistAtomsStorage.getSipTransportSession(0L); - // first atom has count 11, the other has 1 + // First atom has count 11, the other has 1 assertHasStats(result, mSipTransportSession1, 11); assertHasStats(result, mSipTransportSession2, 1); } @@ -3398,6 +3606,599 @@ public class PersistAtomsStorageTest extends TelephonyTest { inOrder.verifyNoMoreInteractions(); } + @Test + public void addOutgoingShortCodeSms_emptyProto() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms1); + mPersistAtomsStorage.incTimeMillis(100L); + + // OutgoingShortCodeSms should be added successfully, changes should be saved. + verifyCurrentStateSavedToFileOnce(); + OutgoingShortCodeSms[] expectedList = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1}; + assertProtoArrayEquals(expectedList, + mPersistAtomsStorage.getOutgoingShortCodeSms(0L)); + } + + @Test + public void addOutgoingShortCodeSms_withExistingEntries() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms1); + mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms2); + mPersistAtomsStorage.incTimeMillis(100L); + + // OutgoingShortCodeSms should be added successfully. + verifyCurrentStateSavedToFileOnce(); + OutgoingShortCodeSms[] expectedList = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1, + mOutgoingShortCodeSms2}; + assertProtoArrayEqualsIgnoringOrder(expectedList, + mPersistAtomsStorage.getOutgoingShortCodeSms(0L)); + } + + @Test + public void addOutgoingShortCodeSms_updateExistingEntries() throws Exception { + createTestFile(START_TIME_MILLIS); + + // Add copy of mOutgoingShortCodeSms1. + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addOutgoingShortCodeSms(copyOf(mOutgoingShortCodeSms1)); + mPersistAtomsStorage.incTimeMillis(100L); + + // mOutgoingShortCodeSms1's short code sms count should be increased by 1. + verifyCurrentStateSavedToFileOnce(); + OutgoingShortCodeSms newOutgoingShortCodeSms1 = copyOf(mOutgoingShortCodeSms1); + newOutgoingShortCodeSms1.shortCodeSmsCount = 2; + OutgoingShortCodeSms[] expectedList = new OutgoingShortCodeSms[] {newOutgoingShortCodeSms1, + mOutgoingShortCodeSms2}; + assertProtoArrayEqualsIgnoringOrder(expectedList, + mPersistAtomsStorage.getOutgoingShortCodeSms(0L)); + } + + @Test + public void addOutgoingShortCodeSms_tooManyEntries() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + // Store mOutgoingShortCodeSms1 11 times. + for (int i = 0; i < 11; i++) { + mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms1); + mPersistAtomsStorage.incTimeMillis(100L); + } + // Store mOutgoingShortCodeSms2 1 time. + mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms2); + + verifyCurrentStateSavedToFileOnce(); + OutgoingShortCodeSms[] result = mPersistAtomsStorage + .getOutgoingShortCodeSms(0L); + assertHasStatsAndCount(result, mOutgoingShortCodeSms1, 11); + assertHasStatsAndCount(result, mOutgoingShortCodeSms2, 1); + } + + @Test + public void getOutgoingShortCodeSms_tooFrequent() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + // Pull interval less than minimum. + mPersistAtomsStorage.incTimeMillis(50L); + OutgoingShortCodeSms[] outgoingShortCodeSmsList = mPersistAtomsStorage + .getOutgoingShortCodeSms(100L); + // Should be denied. + assertNull(outgoingShortCodeSmsList); + } + + @Test + public void getOutgoingShortCodeSms_withSavedAtoms() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.incTimeMillis(100L); + OutgoingShortCodeSms[] outgoingShortCodeSmsList1 = mPersistAtomsStorage + .getOutgoingShortCodeSms(50L); + mPersistAtomsStorage.incTimeMillis(100L); + OutgoingShortCodeSms[] outgoingShortCodeSmsList2 = mPersistAtomsStorage + .getOutgoingShortCodeSms(50L); + + // First set of results should be equal to file contents. + OutgoingShortCodeSms[] expectedOutgoingShortCodeSmsList = + new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1, mOutgoingShortCodeSms2}; + assertProtoArrayEqualsIgnoringOrder(expectedOutgoingShortCodeSmsList, + outgoingShortCodeSmsList1); + // Second set of results should be empty. + assertProtoArrayEquals(new OutgoingShortCodeSms[0], outgoingShortCodeSmsList2); + // Corresponding pull timestamp should be updated and saved. + assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage + .getAtomsProto().outgoingShortCodeSmsPullTimestampMillis); + InOrder inOrder = inOrder(mTestFileOutputStream); + assertEquals(START_TIME_MILLIS + 100L, + getAtomsWritten(inOrder).outgoingShortCodeSmsPullTimestampMillis); + assertEquals(START_TIME_MILLIS + 200L, + getAtomsWritten(inOrder).outgoingShortCodeSmsPullTimestampMillis); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void addSatelliteControllerStats_emptyProto() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController1); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteController[] output = + mPersistAtomsStorage.getSatelliteControllerStats(0L); + assertProtoArrayEquals( + new SatelliteController[] {mSatelliteController1}, output); + } + + @Test + public void addSatelliteControllerStats_withExistingEntries() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController1); + mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController2); + mPersistAtomsStorage.incTimeMillis(100L); + + SatelliteController expected = new SatelliteController(); + expected.countOfSatelliteServiceEnablementsSuccess = + mSatelliteController1.countOfSatelliteServiceEnablementsSuccess + + mSatelliteController2.countOfSatelliteServiceEnablementsSuccess; + expected.countOfSatelliteServiceEnablementsFail = + mSatelliteController1.countOfSatelliteServiceEnablementsFail + + mSatelliteController2.countOfSatelliteServiceEnablementsFail; + expected.countOfOutgoingDatagramSuccess = + mSatelliteController1.countOfOutgoingDatagramSuccess + + mSatelliteController2.countOfOutgoingDatagramSuccess; + expected.countOfOutgoingDatagramFail = + mSatelliteController1.countOfOutgoingDatagramFail + + mSatelliteController2.countOfOutgoingDatagramFail; + expected.countOfIncomingDatagramSuccess = + mSatelliteController1.countOfIncomingDatagramSuccess + + mSatelliteController2.countOfIncomingDatagramSuccess; + expected.countOfIncomingDatagramFail = + mSatelliteController1.countOfIncomingDatagramFail + + mSatelliteController2.countOfIncomingDatagramFail; + expected.countOfDatagramTypeSosSmsSuccess = + mSatelliteController1.countOfDatagramTypeSosSmsSuccess + + mSatelliteController2.countOfDatagramTypeSosSmsSuccess; + expected.countOfDatagramTypeSosSmsFail = + mSatelliteController1.countOfDatagramTypeSosSmsFail + + mSatelliteController2.countOfDatagramTypeSosSmsFail; + expected.countOfDatagramTypeLocationSharingSuccess = + mSatelliteController1.countOfDatagramTypeLocationSharingSuccess + + mSatelliteController2.countOfDatagramTypeLocationSharingSuccess; + expected.countOfDatagramTypeLocationSharingFail = + mSatelliteController1.countOfDatagramTypeLocationSharingFail + + mSatelliteController2.countOfDatagramTypeLocationSharingFail; + expected.countOfProvisionSuccess = + mSatelliteController1.countOfProvisionSuccess + + mSatelliteController2.countOfProvisionSuccess; + expected.countOfProvisionFail = + mSatelliteController1.countOfProvisionFail + + mSatelliteController2.countOfProvisionFail; + expected.countOfDeprovisionSuccess = + mSatelliteController1.countOfDeprovisionSuccess + + mSatelliteController2.countOfDeprovisionSuccess; + expected.countOfDeprovisionFail = + mSatelliteController1.countOfDeprovisionFail + + mSatelliteController2.countOfDeprovisionFail; + expected.totalServiceUptimeSec = + mSatelliteController1.totalServiceUptimeSec + + mSatelliteController2.totalServiceUptimeSec; + expected.totalBatteryConsumptionPercent = + mSatelliteController1.totalBatteryConsumptionPercent + + mSatelliteController2.totalBatteryConsumptionPercent; + expected.totalBatteryChargedTimeSec = + mSatelliteController1.totalBatteryChargedTimeSec + + mSatelliteController2.totalBatteryChargedTimeSec; + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteController[] output = + mPersistAtomsStorage.getSatelliteControllerStats(0L); + assertHasStats(output, expected); + } + + @Test + public void getSatelliteControllerStats_tooFrequent() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum + SatelliteController[] output = + mPersistAtomsStorage.getSatelliteControllerStats(100L); + + // Should be denied + assertNull(output); + } + + @Test + public void addSatelliteSessionStats_emptyProto() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteSessionStats( + mSatelliteSession1); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteSession[] output = + mPersistAtomsStorage.getSatelliteSessionStats(0L); + assertProtoArrayEquals( + new SatelliteSession[] {mSatelliteSession1}, output); + } + + @Test + public void addSatelliteSessionStats_withExistingEntries() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteSessionStats( + mSatelliteSession1); + mPersistAtomsStorage.addSatelliteSessionStats( + mSatelliteSession2); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteSession[] output = + mPersistAtomsStorage.getSatelliteSessionStats(0L); + assertProtoArrayEqualsIgnoringOrder( + new SatelliteSession[] { + mSatelliteSession1, mSatelliteSession2}, + output); + } + + @Test + public void addSatelliteSessionStats_tooManyEntries() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + // Store atoms up to maximum number + 1 + int maxCount = 15 + 1; + for (int i = 0; i < maxCount; i++) { + mPersistAtomsStorage + .addSatelliteSessionStats( + copyOf(mSatelliteSession1)); + mPersistAtomsStorage.incTimeMillis(100L); + } + + // Store 1 different atom + mPersistAtomsStorage + .addSatelliteSessionStats(mSatelliteSession2); + + verifyCurrentStateSavedToFileOnce(); + + SatelliteSession[] result = + mPersistAtomsStorage.getSatelliteSessionStats(0L); + + // First atom has count 16, the other has 1 + assertHasStatsAndCount(result, mSatelliteSession1, 16); + assertHasStatsAndCount(result, mSatelliteSession2, 1); + + } + + @Test + public void getSatelliteSessionStats_tooFrequent() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum + SatelliteSession[] output = + mPersistAtomsStorage.getSatelliteSessionStats(100L); + + // Should be denied + assertNull(output); + } + + + + @Test + public void addSatelliteIncomingDatagramStats_emptyProto() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram1); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteIncomingDatagram[] output = + mPersistAtomsStorage.getSatelliteIncomingDatagramStats(0L); + assertProtoArrayEquals( + new SatelliteIncomingDatagram[] {mSatelliteIncomingDatagram1}, output); + } + + @Test + public void addSatelliteIncomingDatagramStats_withExistingEntries() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram1); + mPersistAtomsStorage.addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram2); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteIncomingDatagram[] output = + mPersistAtomsStorage.getSatelliteIncomingDatagramStats(0L); + assertProtoArrayEqualsIgnoringOrder( + new SatelliteIncomingDatagram[] { + mSatelliteIncomingDatagram1, mSatelliteIncomingDatagram2}, output); + } + + @Test + public void addSatelliteIncomingDatagramStats_tooManyEntries() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + // Store atoms up to maximum number + 1 + int maxCount = 15 + 1; + for (int i = 0; i < maxCount; i++) { + mPersistAtomsStorage + .addSatelliteIncomingDatagramStats(copyOf(mSatelliteIncomingDatagram1)); + mPersistAtomsStorage.incTimeMillis(100L); + } + + // Store 1 different atom + mPersistAtomsStorage + .addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram2); + + verifyCurrentStateSavedToFileOnce(); + + SatelliteIncomingDatagram[] result = + mPersistAtomsStorage.getSatelliteIncomingDatagramStats(0L); + + // First atom has count 14, the other has 1 + assertHasStatsAndCount(result, mSatelliteIncomingDatagram1, 14); + assertHasStatsAndCount(result, mSatelliteIncomingDatagram2, 1); + + } + + @Test + public void getSatelliteIncomingDatagramStats_tooFrequent() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum + SatelliteIncomingDatagram[] output = + mPersistAtomsStorage.getSatelliteIncomingDatagramStats(100L); + + // Should be denied + assertNull(output); + } + + @Test + public void addSatelliteOutgoingDatagramStats_emptyProto() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram1); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteOutgoingDatagram[] output = + mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(0L); + assertProtoArrayEquals( + new SatelliteOutgoingDatagram[] {mSatelliteOutgoingDatagram1}, output); + } + + @Test + public void addSatelliteOutgoingDatagramStats_withExistingEntries() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram1); + mPersistAtomsStorage.addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram2); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteOutgoingDatagram[] output = + mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(0L); + assertProtoArrayEqualsIgnoringOrder( + new SatelliteOutgoingDatagram[] { + mSatelliteOutgoingDatagram1, mSatelliteOutgoingDatagram2}, output); + } + + @Test + public void addSatelliteOutgoingDatagramStats_tooManyEntries() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + // Store atoms up to maximum number + 1 + int maxCount = 15 + 1; + for (int i = 0; i < maxCount; i++) { + mPersistAtomsStorage + .addSatelliteOutgoingDatagramStats(copyOf(mSatelliteOutgoingDatagram1)); + mPersistAtomsStorage.incTimeMillis(100L); + } + + // Store 1 different atom + mPersistAtomsStorage + .addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram2); + + verifyCurrentStateSavedToFileOnce(); + + SatelliteOutgoingDatagram[] result = + mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(0L); + + // First atom has count 14, the other has 1 + assertHasStatsAndCount(result, mSatelliteOutgoingDatagram1, 14); + assertHasStatsAndCount(result, mSatelliteOutgoingDatagram2, 1); + + } + + @Test + public void getSatelliteOutgoingDatagramStats_tooFrequent() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum + SatelliteOutgoingDatagram[] output = + mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(100L); + + // Should be denied + assertNull(output); + } + + @Test + public void addSatelliteProvisionStats_emptyProto() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteProvisionStats(mSatelliteProvision1); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteProvision[] output = + mPersistAtomsStorage.getSatelliteProvisionStats(0L); + assertProtoArrayEquals( + new SatelliteProvision[] {mSatelliteProvision1}, output); + } + + @Test + public void addSatelliteProvisionStats_withExistingEntries() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteProvisionStats(mSatelliteProvision1); + mPersistAtomsStorage.addSatelliteProvisionStats(mSatelliteProvision2); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteProvision[] output = + mPersistAtomsStorage.getSatelliteProvisionStats(0L); + assertProtoArrayEqualsIgnoringOrder( + new SatelliteProvision[] { + mSatelliteProvision1, mSatelliteProvision2}, output); + } + + @Test + public void addSatelliteProvisionStats_tooManyEntries() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + // Store atoms up to maximum number + 1 + int maxCount = 15 + 1; + for (int i = 0; i < maxCount; i++) { + mPersistAtomsStorage + .addSatelliteProvisionStats(copyOf(mSatelliteProvision1)); + mPersistAtomsStorage.incTimeMillis(100L); + } + + // Store 1 different atom + mPersistAtomsStorage + .addSatelliteProvisionStats(mSatelliteProvision2); + + verifyCurrentStateSavedToFileOnce(); + + SatelliteProvision[] result = + mPersistAtomsStorage.getSatelliteProvisionStats(0L); + + // First atom has count 14, the other has 1 + assertHasStatsAndCount(result, mSatelliteProvision1, 14); + assertHasStatsAndCount(result, mSatelliteProvision2, 1); + + } + + @Test + public void getSatelliteProvisionStats_tooFrequent() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum + SatelliteProvision[] output = + mPersistAtomsStorage.getSatelliteProvisionStats(100L); + + // Should be denied + assertNull(output); + } + + @Test + public void addSatelliteSosMessageRecommenderStats_emptyProto() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteSosMessageRecommenderStats( + mSatelliteSosMessageRecommender1); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteSosMessageRecommender[] output = + mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(0L); + assertProtoArrayEquals( + new SatelliteSosMessageRecommender[] {mSatelliteSosMessageRecommender1}, output); + } + + @Test + public void addSatelliteSosMessageRecommenderStats_withExistingEntries() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.addSatelliteSosMessageRecommenderStats( + mSatelliteSosMessageRecommender1); + mPersistAtomsStorage.addSatelliteSosMessageRecommenderStats( + mSatelliteSosMessageRecommender2); + mPersistAtomsStorage.incTimeMillis(100L); + + // Service state and service switch should be added successfully + verifyCurrentStateSavedToFileOnce(); + SatelliteSosMessageRecommender[] output = + mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(0L); + assertProtoArrayEqualsIgnoringOrder( + new SatelliteSosMessageRecommender[] { + mSatelliteSosMessageRecommender1, mSatelliteSosMessageRecommender2}, + output); + } + + @Test + public void addSatelliteSosMessageRecommenderStats_tooManyEntries() throws Exception { + createEmptyTestFile(); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + // Store atoms up to maximum number + 1 + int maxCount = 15 + 1; + for (int i = 0; i < maxCount; i++) { + mPersistAtomsStorage + .addSatelliteSosMessageRecommenderStats( + copyOf(mSatelliteSosMessageRecommender1)); + mPersistAtomsStorage.incTimeMillis(100L); + } + + // Store 1 different atom + mPersistAtomsStorage + .addSatelliteSosMessageRecommenderStats(mSatelliteSosMessageRecommender2); + + verifyCurrentStateSavedToFileOnce(); + + SatelliteSosMessageRecommender[] result = + mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(0L); + + // First atom has count 16, the other has 1 + assertHasStatsAndCount(result, mSatelliteSosMessageRecommender1, 16); + assertHasStatsAndCount(result, mSatelliteSosMessageRecommender2, 1); + + } + + @Test + public void getSatelliteSosMessageRecommenderStats_tooFrequent() throws Exception { + createTestFile(START_TIME_MILLIS); + + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum + SatelliteSosMessageRecommender[] output = + mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(100L); + + // Should be denied + assertNull(output); + } + @Test @SmallTest public void clearAtoms() throws Exception { @@ -3469,6 +4270,20 @@ public class PersistAtomsStorageTest extends TelephonyTest { atoms.sipMessageResponse = mSipMessageResponse; atoms.sipTransportSessionPullTimestampMillis = lastPullTimeMillis; atoms.sipTransportSession = mSipTransportSession; + atoms.outgoingShortCodeSms = mOutgoingShortCodeSms; + atoms.outgoingShortCodeSmsPullTimestampMillis = lastPullTimeMillis; + atoms.satelliteController = mSatelliteControllers; + atoms.satelliteControllerPullTimestampMillis = lastPullTimeMillis; + atoms.satelliteSession = mSatelliteSessions; + atoms.satelliteSessionPullTimestampMillis = lastPullTimeMillis; + atoms.satelliteIncomingDatagram = mSatelliteIncomingDatagrams; + atoms.satelliteIncomingDatagramPullTimestampMillis = lastPullTimeMillis; + atoms.satelliteOutgoingDatagram = mSatelliteOutgoingDatagrams; + atoms.satelliteOutgoingDatagramPullTimestampMillis = lastPullTimeMillis; + atoms.satelliteProvision = mSatelliteProvisions; + atoms.satelliteProvisionPullTimestampMillis = lastPullTimeMillis; + atoms.satelliteSosMessageRecommender = mSatelliteSosMessageRecommenders; + atoms.satelliteSosMessageRecommenderPullTimestampMillis = lastPullTimeMillis; FileOutputStream stream = new FileOutputStream(mTestFile); stream.write(PersistAtoms.toByteArray(atoms)); stream.close(); @@ -3587,6 +4402,41 @@ public class PersistAtomsStorageTest extends TelephonyTest { return SipTransportSession.parseFrom(MessageNano.toByteArray(source)); } + private static OutgoingShortCodeSms copyOf(OutgoingShortCodeSms source) + throws Exception { + return OutgoingShortCodeSms.parseFrom(MessageNano.toByteArray(source)); + } + + private static SatelliteController copyOf(SatelliteController source) + throws Exception { + return SatelliteController.parseFrom(MessageNano.toByteArray(source)); + } + + private static SatelliteSession copyOf(SatelliteSession source) + throws Exception { + return SatelliteSession.parseFrom(MessageNano.toByteArray(source)); + } + + private static SatelliteIncomingDatagram copyOf(SatelliteIncomingDatagram source) + throws Exception { + return SatelliteIncomingDatagram.parseFrom(MessageNano.toByteArray(source)); + } + + private static SatelliteOutgoingDatagram copyOf(SatelliteOutgoingDatagram source) + throws Exception { + return SatelliteOutgoingDatagram.parseFrom(MessageNano.toByteArray(source)); + } + + private static SatelliteProvision copyOf(SatelliteProvision source) + throws Exception { + return SatelliteProvision.parseFrom(MessageNano.toByteArray(source)); + } + + private static SatelliteSosMessageRecommender copyOf(SatelliteSosMessageRecommender source) + throws Exception { + return SatelliteSosMessageRecommender.parseFrom(MessageNano.toByteArray(source)); + } + private void assertAllPullTimestampEquals(long timestamp) { assertEquals( timestamp, @@ -3732,6 +4582,119 @@ public class PersistAtomsStorageTest extends TelephonyTest { assertEquals(expectedCount, actualCount); } + private static void assertHasStats( + SatelliteController[] tested, + @Nullable SatelliteController expectedStats) { + assertNotNull(tested); + assertEquals(tested[0].countOfSatelliteServiceEnablementsSuccess, + expectedStats.countOfSatelliteServiceEnablementsSuccess); + assertEquals(tested[0].countOfSatelliteServiceEnablementsFail, + expectedStats.countOfSatelliteServiceEnablementsFail); + assertEquals(tested[0].countOfOutgoingDatagramSuccess, + expectedStats.countOfOutgoingDatagramSuccess); + assertEquals(tested[0].countOfOutgoingDatagramFail, + expectedStats.countOfOutgoingDatagramFail); + assertEquals(tested[0].countOfIncomingDatagramSuccess, + expectedStats.countOfIncomingDatagramSuccess); + assertEquals(tested[0].countOfIncomingDatagramFail, + expectedStats.countOfIncomingDatagramFail); + assertEquals(tested[0].countOfDatagramTypeSosSmsSuccess, + expectedStats.countOfDatagramTypeSosSmsSuccess); + assertEquals(tested[0].countOfDatagramTypeSosSmsFail, + expectedStats.countOfDatagramTypeSosSmsFail); + assertEquals(tested[0].countOfDatagramTypeLocationSharingSuccess, + expectedStats.countOfDatagramTypeLocationSharingSuccess); + assertEquals(tested[0].countOfDatagramTypeLocationSharingFail, + expectedStats.countOfDatagramTypeLocationSharingFail); + assertEquals(tested[0].totalServiceUptimeSec, + expectedStats.totalServiceUptimeSec); + assertEquals(tested[0].totalBatteryConsumptionPercent, + expectedStats.totalBatteryConsumptionPercent); + assertEquals(tested[0].totalBatteryChargedTimeSec, + expectedStats.totalBatteryChargedTimeSec); + } + + private static void assertHasStatsAndCount( + SatelliteSession[] tested, + @Nullable SatelliteSession expectedStats, int expectedCount) { + assertNotNull(tested); + int actualCount = 0; + for (SatelliteSession stats : tested) { + if (stats.satelliteServiceInitializationResult + == expectedStats.satelliteServiceInitializationResult + && stats.satelliteTechnology == expectedStats.satelliteTechnology) { + actualCount = stats.count; + } + } + assertEquals(expectedCount, actualCount); + } + + private static void assertHasStatsAndCount( + SatelliteIncomingDatagram[] tested, + @Nullable SatelliteIncomingDatagram expectedStats, int expectedCount) { + assertNotNull(tested); + int actualCount = 0; + for (SatelliteIncomingDatagram stats : tested) { + if (stats.resultCode == expectedStats.resultCode + && stats.datagramSizeBytes == expectedStats.datagramSizeBytes + && stats.datagramTransferTimeMillis + == expectedStats.datagramTransferTimeMillis) { + actualCount++; + } + } + assertEquals(expectedCount, actualCount); + } + + private static void assertHasStatsAndCount( + SatelliteOutgoingDatagram[] tested, + @Nullable SatelliteOutgoingDatagram expectedStats, int expectedCount) { + assertNotNull(tested); + int actualCount = 0; + for (SatelliteOutgoingDatagram stats : tested) { + if (stats.datagramType == expectedStats.datagramType + && stats.resultCode == expectedStats.resultCode + && stats.datagramSizeBytes == expectedStats.datagramSizeBytes + && stats.datagramTransferTimeMillis + == expectedStats.datagramTransferTimeMillis) { + actualCount++; + } + } + assertEquals(expectedCount, actualCount); + } + + private static void assertHasStatsAndCount( + SatelliteProvision[] tested, + @Nullable SatelliteProvision expectedStats, int expectedCount) { + assertNotNull(tested); + int actualCount = 0; + for (SatelliteProvision stats : tested) { + if (stats.resultCode == expectedStats.resultCode + && stats.provisioningTimeSec == expectedStats.provisioningTimeSec + && stats.isProvisionRequest == expectedStats.isProvisionRequest + && stats.isCanceled == expectedStats.isCanceled) { + actualCount++; + } + } + assertEquals(expectedCount, actualCount); + } + + private static void assertHasStatsAndCount( + SatelliteSosMessageRecommender[] tested, + @Nullable SatelliteSosMessageRecommender expectedStats, int expectedCount) { + assertNotNull(tested); + int actualCount = 0; + for (SatelliteSosMessageRecommender stats : tested) { + if (stats.isDisplaySosMessageSent + == expectedStats.isDisplaySosMessageSent + && stats.countOfTimerStarted == expectedStats.countOfTimerStarted + && stats.isImsRegistered == expectedStats.isImsRegistered + && stats.cellularServiceState == expectedStats.cellularServiceState) { + actualCount = stats.count; + } + } + assertEquals(expectedCount, actualCount); + } + private static void assertHasStatsAndCountDuration( RcsAcsProvisioningStats[] statses, @Nullable RcsAcsProvisioningStats expectedStats, int count, long duration) { @@ -3866,4 +4829,18 @@ public class PersistAtomsStorageTest extends TelephonyTest { } assertEquals(expectedCount, actualCount); } + + private static void assertHasStatsAndCount( + OutgoingShortCodeSms[] outgoingShortCodeSmsList, + @Nullable OutgoingShortCodeSms expectedOutgoingShortCodeSms, int expectedCount) { + assertNotNull(outgoingShortCodeSmsList); + int actualCount = -1; + for (OutgoingShortCodeSms outgoingShortCodeSms : outgoingShortCodeSmsList) { + if (outgoingShortCodeSms.category == expectedOutgoingShortCodeSms.category + && outgoingShortCodeSms.xmlVersion == expectedOutgoingShortCodeSms.xmlVersion) { + actualCount = outgoingShortCodeSms.shortCodeSmsCount; + } + } + assertEquals(expectedCount, actualCount); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..22032ae88ae8f342e91c851b45e8ef81a096118d --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.metrics; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.telephony.SatelliteProtoEnums; +import android.telephony.TelephonyProtoEnums; + +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession; +import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class SatelliteStatsTest extends TelephonyTest { + private static final String TAG = SatelliteStatsTest.class.getSimpleName(); + + private TestableSatelliteStats mSatelliteStats; + + private class TestableSatelliteStats extends SatelliteStats { + TestableSatelliteStats() { + super(); + } + } + + @Before + public void setup() throws Exception { + super.setUp(getClass().getSimpleName()); + + mSatelliteStats = new TestableSatelliteStats(); + } + + @After + public void tearDown() throws Exception { + mSatelliteStats = null; + super.tearDown(); + } + + @Test + public void onSatelliteControllerMetrics_withAtoms() throws Exception { + SatelliteStats.SatelliteControllerParams param = + new SatelliteStats.SatelliteControllerParams.Builder() + .setCountOfSatelliteServiceEnablementsSuccess(2) + .setCountOfSatelliteServiceEnablementsFail(2) + .setCountOfOutgoingDatagramSuccess(8) + .setCountOfOutgoingDatagramFail(9) + .setCountOfIncomingDatagramSuccess(10) + .setCountOfIncomingDatagramFail(11) + .setCountOfDatagramTypeSosSmsSuccess(5) + .setCountOfDatagramTypeSosSmsFail(5) + .setCountOfDatagramTypeLocationSharingSuccess(6) + .setCountOfDatagramTypeLocationSharingFail(6) + .setCountOfProvisionSuccess(3) + .setCountOfProvisionFail(4) + .setCountOfDeprovisionSuccess(5) + .setCountOfDeprovisionFail(6) + .setTotalServiceUptimeSec(60 * 60 * 24 * 7) + .setTotalBatteryConsumptionPercent(7) + .setTotalBatteryChargedTimeSec(60 * 60 * 3) + .build(); + + mSatelliteStats.onSatelliteControllerMetrics(param); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(SatelliteController.class); + verify(mPersistAtomsStorage).addSatelliteControllerStats(captor.capture()); + SatelliteController stats = captor.getValue(); + assertEquals(param.getCountOfSatelliteServiceEnablementsSuccess(), + stats.countOfSatelliteServiceEnablementsSuccess); + assertEquals(param.getCountOfSatelliteServiceEnablementsFail(), + stats.countOfSatelliteServiceEnablementsFail); + assertEquals(param.getCountOfOutgoingDatagramSuccess(), + stats.countOfOutgoingDatagramSuccess); + assertEquals(param.getCountOfOutgoingDatagramFail(), + stats.countOfOutgoingDatagramFail); + assertEquals(param.getCountOfIncomingDatagramSuccess(), + stats.countOfIncomingDatagramSuccess); + assertEquals(param.getCountOfIncomingDatagramFail(), + stats.countOfIncomingDatagramFail); + assertEquals(param.getCountOfDatagramTypeSosSmsSuccess(), + stats.countOfDatagramTypeSosSmsSuccess); + assertEquals(param.getCountOfDatagramTypeSosSmsFail(), + stats.countOfDatagramTypeSosSmsFail); + assertEquals(param.getCountOfDatagramTypeLocationSharingSuccess(), + stats.countOfDatagramTypeLocationSharingSuccess); + assertEquals(param.getCountOfDatagramTypeLocationSharingFail(), + stats.countOfDatagramTypeLocationSharingFail); + assertEquals(param.getCountOfProvisionSuccess(), + stats.countOfProvisionSuccess); + assertEquals(param.getCountOfProvisionFail(), + stats.countOfProvisionFail); + assertEquals(param.getCountOfDeprovisionSuccess(), + stats.countOfDeprovisionSuccess); + assertEquals(param.getCountOfDeprovisionFail(), + stats.countOfDeprovisionFail); + assertEquals(param.getTotalServiceUptimeSec(), + stats.totalServiceUptimeSec); + assertEquals(param.getTotalBatteryConsumptionPercent(), + stats.totalBatteryConsumptionPercent); + assertEquals(param.getTotalBatteryChargedTimeSec(), + stats.totalBatteryChargedTimeSec); + + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void onSatelliteSessionMetrics_withAtoms() throws Exception { + SatelliteStats.SatelliteSessionParams param = + new SatelliteStats.SatelliteSessionParams.Builder() + .setSatelliteServiceInitializationResult( + SatelliteProtoEnums.SATELLITE_ERROR_NONE) + .setSatelliteTechnology(SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY) + .build(); + + mSatelliteStats.onSatelliteSessionMetrics(param); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(SatelliteSession.class); + verify(mPersistAtomsStorage).addSatelliteSessionStats(captor.capture()); + SatelliteSession stats = captor.getValue(); + assertEquals(param.getSatelliteServiceInitializationResult(), + stats.satelliteServiceInitializationResult); + assertEquals(param.getSatelliteTechnology(), stats.satelliteTechnology); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void onSatelliteIncomingDatagramMetrics_withAtoms() throws Exception { + SatelliteStats.SatelliteIncomingDatagramParams param = + new SatelliteStats.SatelliteIncomingDatagramParams.Builder() + .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE) + .setDatagramSizeBytes(1 * 1024) + .setDatagramTransferTimeMillis(3 * 1000) + .build(); + + mSatelliteStats.onSatelliteIncomingDatagramMetrics(param); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(SatelliteIncomingDatagram.class); + verify(mPersistAtomsStorage).addSatelliteIncomingDatagramStats(captor.capture()); + SatelliteIncomingDatagram stats = captor.getValue(); + assertEquals(param.getResultCode(), stats.resultCode); + assertEquals(param.getDatagramSizeBytes(), stats.datagramSizeBytes); + assertEquals(param.getDatagramTransferTimeMillis(), stats.datagramTransferTimeMillis); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void onSatelliteOutgoingDatagramMetrics_withAtoms() throws Exception { + SatelliteStats.SatelliteOutgoingDatagramParams param = + new SatelliteStats.SatelliteOutgoingDatagramParams.Builder() + .setDatagramType(SatelliteProtoEnums.DATAGRAM_TYPE_LOCATION_SHARING) + .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE) + .setDatagramSizeBytes(1 * 1024) + .setDatagramTransferTimeMillis(3 * 1000) + .build(); + + mSatelliteStats.onSatelliteOutgoingDatagramMetrics(param); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(SatelliteOutgoingDatagram.class); + verify(mPersistAtomsStorage).addSatelliteOutgoingDatagramStats(captor.capture()); + SatelliteOutgoingDatagram stats = captor.getValue(); + assertEquals(param.getDatagramType(), stats.datagramType); + assertEquals(param.getResultCode(), stats.resultCode); + assertEquals(param.getDatagramSizeBytes(), stats.datagramSizeBytes); + assertEquals(param.getDatagramTransferTimeMillis(), stats.datagramTransferTimeMillis); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void onSatelliteProvisionMetrics_withAtoms() throws Exception { + SatelliteStats.SatelliteProvisionParams param = + new SatelliteStats.SatelliteProvisionParams.Builder() + .setResultCode(SatelliteProtoEnums.SATELLITE_SERVICE_PROVISION_IN_PROGRESS) + .setProvisioningTimeSec(5 * 1000) + .setIsProvisionRequest(true) + .setIsCanceled(false) + .build(); + + mSatelliteStats.onSatelliteProvisionMetrics(param); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(SatelliteProvision.class); + verify(mPersistAtomsStorage).addSatelliteProvisionStats(captor.capture()); + SatelliteProvision stats = captor.getValue(); + assertEquals(param.getResultCode(), stats.resultCode); + assertEquals(param.getProvisioningTimeSec(), stats.provisioningTimeSec); + assertEquals(param.getIsProvisionRequest(), stats.isProvisionRequest); + assertEquals(param.getIsCanceled(), stats.isCanceled); + + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void onSatelliteSosMessageRecommenderMetrics_withAtoms() throws Exception { + SatelliteStats.SatelliteSosMessageRecommenderParams param = + new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder() + .setDisplaySosMessageSent(true) + .setCountOfTimerStarted(5) + .setImsRegistered(false) + .setCellularServiceState(TelephonyProtoEnums.SERVICE_STATE_OUT_OF_SERVICE) + .build(); + + mSatelliteStats.onSatelliteSosMessageRecommender(param); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(SatelliteSosMessageRecommender.class); + verify(mPersistAtomsStorage).addSatelliteSosMessageRecommenderStats(captor.capture()); + SatelliteSosMessageRecommender stats = captor.getValue(); + assertEquals(param.isDisplaySosMessageSent(), + stats.isDisplaySosMessageSent); + assertEquals(param.getCountOfTimerStarted(), stats.countOfTimerStarted); + assertEquals(param.isImsRegistered(), stats.isImsRegistered); + assertEquals(param.getCellularServiceState(), stats.cellularServiceState); + verifyNoMoreInteractions(mPersistAtomsStorage); + } +} 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 8406bc5cbac1d46d1fbec10cab95fa57200383c0..7b66a52e40c449104509106cc5573f995ae3fc8d 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java @@ -16,6 +16,15 @@ package com.android.internal.telephony.metrics; +import static android.telephony.TelephonyManager.DATA_CONNECTED; +import static android.telephony.TelephonyManager.DATA_UNKNOWN; + +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED; +import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN; +import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; +import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; +import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -35,10 +44,6 @@ import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; -import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; -import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; -import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN; - import com.android.internal.telephony.Phone; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; @@ -109,6 +114,9 @@ public class ServiceStateStatsTest extends TelephonyTest { mockWwanPsRat(TelephonyManager.NETWORK_TYPE_LTE); doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech(); + doReturn(DATA_CONNECTED).when(mDataNetworkController).getInternetDataNetworkState(); + doReturn(mDataNetworkController).when(mSecondPhone).getDataNetworkController(); + mServiceStateStats = new TestableServiceStateStats(mPhone); } @@ -143,6 +151,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -155,6 +164,7 @@ public class ServiceStateStatsTest extends TelephonyTest { doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getDataNetworkType(); mockWwanCsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN); mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN); + doReturn(DATA_UNKNOWN).when(mDataNetworkController).getInternetDataNetworkState(); mServiceStateStats.onServiceStateChanged(mServiceState); mServiceStateStats.incTimeMillis(100L); @@ -176,6 +186,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(false, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -221,6 +232,7 @@ public class ServiceStateStatsTest extends TelephonyTest { doReturn(CardState.CARDSTATE_ABSENT).when(mPhysicalSlot0).getCardState(); mockLimitedService(TelephonyManager.NETWORK_TYPE_UMTS); doReturn(-1).when(mPhone).getCarrierId(); + doReturn(DATA_UNKNOWN).when(mDataNetworkController).getInternetDataNetworkState(); mServiceStateStats.onServiceStateChanged(mServiceState); mServiceStateStats.incTimeMillis(100L); @@ -242,6 +254,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(-1, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(true, state.isEmergencyOnly); + assertEquals(false, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -256,6 +269,7 @@ public class ServiceStateStatsTest extends TelephonyTest { mockWwanCsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN); mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN); doReturn(-1).when(mPhone).getCarrierId(); + doReturn(DATA_UNKNOWN).when(mDataNetworkController).getInternetDataNetworkState(); mServiceStateStats.onServiceStateChanged(mServiceState); mServiceStateStats.incTimeMillis(100L); @@ -277,6 +291,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(-1, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(false, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -308,6 +323,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -319,6 +335,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -355,6 +372,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat); @@ -366,6 +384,50 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + @SmallTest + public void onInternetDataNetworkDisconnected() throws Exception { + // Using default service state for LTE + mServiceStateStats.onServiceStateChanged(mServiceState); + + mServiceStateStats.incTimeMillis(100L); + mServiceStateStats.onInternetDataNetworkDisconnected(); + mServiceStateStats.incTimeMillis(200L); + mServiceStateStats.conclude(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(CellularServiceState.class); + verify(mPersistAtomsStorage, times(2)) + .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null)); + assertNotSame(captor.getAllValues().get(0), captor.getAllValues().get(1)); + CellularServiceState state = captor.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); + state = captor.getAllValues().get(1); + 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(200L, state.totalTimeMillis); + assertEquals(false, state.isEmergencyOnly); + assertEquals(false, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -394,6 +456,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -431,6 +494,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = serviceStateCaptor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -442,6 +506,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, serviceSwitch.ratFrom); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratTo); @@ -482,6 +547,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertFalse(state.isMultiSim); assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -492,6 +558,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertFalse(state.isMultiSim); assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -520,6 +587,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(0L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -560,6 +628,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -571,6 +640,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -582,6 +652,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(400L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(3); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -593,6 +664,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(800L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -637,6 +709,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat); @@ -648,6 +721,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(-1, state.carrierId); assertEquals(5000L, state.totalTimeMillis); assertEquals(true, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -659,6 +733,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER2_ID, state.carrierId); assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -703,6 +778,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = serviceStateCaptor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -714,6 +790,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = serviceStateCaptor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -725,6 +802,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(400L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo); @@ -781,6 +859,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = serviceStateCaptor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -792,6 +871,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = serviceStateCaptor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -803,6 +883,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = serviceStateCaptor.getAllValues().get(3); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -814,6 +895,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo); @@ -874,6 +956,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -885,6 +968,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -911,6 +995,47 @@ public class ServiceStateStatsTest extends TelephonyTest { mPhone, mServiceState, VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN)); } + @Test + @SmallTest + public void onFoldStateChanged_modemOff() throws Exception { + doReturn(ServiceState.STATE_POWER_OFF).when(mServiceState).getVoiceRegState(); + doReturn(ServiceState.STATE_POWER_OFF).when(mServiceState).getDataRegState(); + doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getVoiceNetworkType(); + doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getDataNetworkType(); + mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN); + doReturn(-1).when(mPhone).getCarrierId(); + mServiceStateStats.onServiceStateChanged(mServiceState); + mServiceStateStats.incTimeMillis(100L); + + mServiceStateStats.onFoldStateChanged(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + @SmallTest + public void onFoldStateChanged_LTEMode() throws Exception { + // Using default service state for LTE with fold state unknown + mServiceStateStats.onServiceStateChanged(mServiceState); + mServiceStateStats.incTimeMillis(100L); + mServiceStateStats.onFoldStateChanged(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED); + mServiceStateStats.incTimeMillis(1000L); + // Same fold state as before should not generate a new atom + mServiceStateStats.onFoldStateChanged(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED); + mServiceStateStats.incTimeMillis(1000L); + + // There should be 2 service state updates + mServiceStateStats.conclude(); + ArgumentCaptor captor = + ArgumentCaptor.forClass(CellularServiceState.class); + verify(mPersistAtomsStorage, times(2)) + .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null)); + CellularServiceState state = captor.getAllValues().get(0); + assertEquals(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN, state.foldState); + state = captor.getAllValues().get(1); + assertEquals(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED, state.foldState); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + private void mockWwanPsRat(@NetworkType int rat) { mockWwanRat( NetworkRegistrationInfo.DOMAIN_PS, 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 fb556e60c27b6d5a2dc0fa6b73009f741c0ba2a5..2ca0b167e659dff19113733b01bded48f48affd3 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java @@ -77,7 +77,6 @@ import com.android.internal.telephony.uicc.UiccSlot; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -927,7 +926,6 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { @Test @SmallTest - @Ignore("b/256234604") public void singleImsCall_ratSwitchToUnknown() { setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java index 2ac0c98365b74ffc2aa5ead81abc50f563298817..580d533322b4c0b9d654ab08c209be09a85d4de5 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java @@ -910,7 +910,8 @@ public class NitzStateMachineImplTest { suggestedTimes.set(timeSuggestion); if (timeSuggestion.getUnixEpochTime() != null) { // The fake time service just uses the latest suggestion. - mFakeDeviceState.currentTimeMillis = timeSuggestion.getUnixEpochTime().getValue(); + mFakeDeviceState.currentTimeMillis = + timeSuggestion.getUnixEpochTime().getUnixEpochTimeMillis(); } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaDirectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaDirectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6a91d956511281b83be7b0d354c462d2a4dc17ff --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaDirectionTest.java @@ -0,0 +1,52 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; +import android.telephony.satellite.AntennaDirection; + +import org.junit.Test; + +public class AntennaDirectionTest { + + @Test + public void testParcel() { + AntennaDirection antennaDirection = new AntennaDirection(1, 2, 3); + + Parcel p = Parcel.obtain(); + antennaDirection.writeToParcel(p, 0); + p.setDataPosition(0); + + AntennaDirection fromParcel = AntennaDirection.CREATOR.createFromParcel(p); + assertThat(antennaDirection).isEqualTo(fromParcel); + } + + @Test + public void testEquals() { + AntennaDirection antennaDirection1 = new AntennaDirection(1.12f, 1.13f, 1.14f); + AntennaDirection antennaDirection2 = new AntennaDirection(1.12f, 1.13f, 1.14f); + assertEquals(antennaDirection1, antennaDirection2); + + AntennaDirection antennaDirection3 = new AntennaDirection(1.121f, 1.131f, 1.141f); + assertNotEquals(antennaDirection1, antennaDirection3); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaPositionTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaPositionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..919ab83f402816ccb1c15992b5ebf4cf9d9df9a7 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaPositionTest.java @@ -0,0 +1,60 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; +import android.telephony.satellite.AntennaDirection; +import android.telephony.satellite.AntennaPosition; +import android.telephony.satellite.SatelliteManager; + +import org.junit.Test; + +public class AntennaPositionTest { + + private AntennaDirection mAntennaDirection = new AntennaDirection(1,1,1); + + @Test + public void testParcel() { + AntennaPosition antennaPosition = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT); + + Parcel p = Parcel.obtain(); + antennaPosition.writeToParcel(p, 0); + p.setDataPosition(0); + + AntennaPosition fromParcel = AntennaPosition.CREATOR.createFromParcel(p); + assertThat(antennaPosition).isEqualTo(fromParcel); + } + + @Test + public void testEquals() { + AntennaPosition antennaPosition1 = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT); + AntennaPosition antennaPosition2 = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT); + assertEquals(antennaPosition1, antennaPosition2); + + AntennaPosition antennaPosition3 = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT); + assertNotEquals(antennaPosition1, antennaPosition3); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..baa00c156c70349144f9a1ebf6f554eedfe5aaa4 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java @@ -0,0 +1,607 @@ +/* + * 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.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.telephony.satellite.SatelliteManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.metrics.SatelliteStats; +import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; +import com.android.telephony.Rlog; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ControllerMetricsStatsTest extends TelephonyTest { + private static final String TAG = "ControllerMetricsStatsTest"; + + private static final long MODEM_ENABLED_TIME = 1000L; + + private TestControllerMetricsStats mControllerMetricsStatsUT; + private TestSatelliteStats mTestStats; + + @Mock private Context mMockContext; + @Spy private ControllerMetricsStats mSpyControllerMetricsStats; + @Mock private SatelliteStats mMockSatelliteStats; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + Rlog.d(TAG, "setUp()"); + mTestStats = new TestSatelliteStats(); + mControllerMetricsStatsUT = + new TestControllerMetricsStats(mMockContext, mTestStats); + mMockContext = mock(Context.class); + mMockSatelliteStats = mock(SatelliteStats.class); + mSpyControllerMetricsStats = + Mockito.spy(ControllerMetricsStats.make(mMockContext, mMockSatelliteStats)); + } + + @After + public void tearDown() throws Exception { + Rlog.d(TAG, "tearDown()"); + mTestStats = null; + mControllerMetricsStatsUT = null; + mMockSatelliteStats = null; + mSpyControllerMetricsStats = null; + super.tearDown(); + } + + @Test + public void testReportServiceEnablementSuccessCount() { + mTestStats.initializeParams(); + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportServiceEnablementSuccessCount(); + } + assertEquals(10, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + } + + @Test + public void testReportServiceEnablementFailCount() { + mTestStats.initializeParams(); + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportServiceEnablementFailCount(); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(10, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + } + + @Test + public void testReportOutgoingDatagramSuccessCount() { + mTestStats.initializeParams(); + int datagramType = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(10, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + datagramType = SatelliteManager.DATAGRAM_TYPE_UNKNOWN; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + } + + @Test + public void reportOutgoingDatagramFailCount() { + mTestStats.initializeParams(); + int datagramType = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(10, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + datagramType = SatelliteManager.DATAGRAM_TYPE_UNKNOWN; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + } + + @Test + public void testReportIncomingDatagramCount() { + mTestStats.initializeParams(); + + int result = SatelliteManager.SATELLITE_ERROR_NONE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportIncomingDatagramCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(10, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + result = SatelliteManager.SATELLITE_SERVER_ERROR; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportIncomingDatagramCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(10, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportIncomingDatagramCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(10, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + } + + @Test + public void testReportProvisionCount() { + mTestStats.initializeParams(); + + int result = SatelliteManager.SATELLITE_ERROR_NONE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportProvisionCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(10, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + result = SatelliteManager.SATELLITE_SERVER_ERROR; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportProvisionCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(10, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportProvisionCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(10, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + } + + @Test + public void testReportDeprovisionCount() { + mTestStats.initializeParams(); + + int result = SatelliteManager.SATELLITE_ERROR_NONE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportDeprovisionCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(10, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(0, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + result = SatelliteManager.SATELLITE_SERVER_ERROR; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportDeprovisionCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(10, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + + result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; + for (int i = 0; i < 10; i++) { + mControllerMetricsStatsUT.reportDeprovisionCount(result); + } + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess); + assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail); + assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess); + assertEquals(0, mTestStats.mCountOfIncomingDatagramFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess); + assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail); + assertEquals(0, mTestStats.mCountOfProvisionSuccess); + assertEquals(0, mTestStats.mCountOfProvisionFail); + assertEquals(0, mTestStats.mCountOfDeprovisionSuccess); + assertEquals(10, mTestStats.mCountOfDeprovisionFail); + assertEquals(0, mTestStats.mTotalServiceUptimeSec); + assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent); + assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec); + mTestStats.initializeParams(); + } + + @Test + public void testOnSatelliteEnabled() { + // set precondition + doReturn(false).when(mSpyControllerMetricsStats).isSatelliteModemOn(); + + doNothing().when(mSpyControllerMetricsStats).startCaptureBatteryLevel(); + doReturn(MODEM_ENABLED_TIME).when(mSpyControllerMetricsStats).getCurrentTime(); + + // test object + mSpyControllerMetricsStats.onSatelliteEnabled(); + + // verification + verify(mSpyControllerMetricsStats).startCaptureBatteryLevel(); + verify(mSpyControllerMetricsStats).getCurrentTime(); + } + + @Test + public void testOnSatelliteDisabled() { + // set precondition + doNothing().when(mMockSatelliteStats).onSatelliteControllerMetrics(any()); + + doReturn(true).when(mSpyControllerMetricsStats).isSatelliteModemOn(); + + doReturn(0).when(mSpyControllerMetricsStats).captureTotalServiceUpTimeSec(); + doReturn(0).when(mSpyControllerMetricsStats).captureTotalBatteryConsumptionPercent(any()); + doReturn(0).when(mSpyControllerMetricsStats).captureTotalBatteryChargeTimeSec(); + + // test object + mSpyControllerMetricsStats.onSatelliteDisabled(); + + // verification + verify(mSpyControllerMetricsStats).captureTotalServiceUpTimeSec(); + verify(mSpyControllerMetricsStats).captureTotalBatteryConsumptionPercent(any()); + verify(mSpyControllerMetricsStats).captureTotalBatteryChargeTimeSec(); + } + + static class TestControllerMetricsStats extends ControllerMetricsStats { + TestControllerMetricsStats(Context context, SatelliteStats satelliteStats) { + super(context, satelliteStats); + } + } + + static class TestSatelliteStats extends SatelliteStats { + public int mCountOfSatelliteServiceEnablementsSuccess; + public int mCountOfSatelliteServiceEnablementsFail; + public int mCountOfOutgoingDatagramSuccess; + public int mCountOfOutgoingDatagramFail; + public int mCountOfIncomingDatagramSuccess; + public int mCountOfIncomingDatagramFail; + public int mCountOfDatagramTypeSosSmsSuccess; + public int mCountOfDatagramTypeSosSmsFail; + public int mCountOfDatagramTypeLocationSharingSuccess; + public int mCountOfDatagramTypeLocationSharingFail; + public int mCountOfProvisionSuccess; + public int mCountOfProvisionFail; + public int mCountOfDeprovisionSuccess; + public int mCountOfDeprovisionFail; + public int mTotalServiceUptimeSec; + public int mTotalBatteryConsumptionPercent; + public int mTotalBatteryChargedTimeSec; + + @Override + public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) { + mCountOfSatelliteServiceEnablementsSuccess += + param.getCountOfSatelliteServiceEnablementsSuccess(); + mCountOfSatelliteServiceEnablementsFail += + param.getCountOfSatelliteServiceEnablementsFail(); + mCountOfOutgoingDatagramSuccess += param.getCountOfOutgoingDatagramSuccess(); + mCountOfOutgoingDatagramFail += param.getCountOfOutgoingDatagramFail(); + mCountOfIncomingDatagramSuccess += param.getCountOfIncomingDatagramSuccess(); + mCountOfIncomingDatagramFail += param.getCountOfIncomingDatagramFail(); + mCountOfDatagramTypeSosSmsSuccess += param.getCountOfDatagramTypeSosSmsSuccess(); + mCountOfDatagramTypeSosSmsFail += param.getCountOfDatagramTypeSosSmsFail(); + mCountOfDatagramTypeLocationSharingSuccess += + param.getCountOfDatagramTypeLocationSharingSuccess(); + mCountOfDatagramTypeLocationSharingFail += + param.getCountOfDatagramTypeLocationSharingFail(); + mCountOfProvisionSuccess += param.getCountOfProvisionSuccess(); + mCountOfProvisionFail += param.getCountOfProvisionFail(); + mCountOfDeprovisionSuccess += param.getCountOfDeprovisionSuccess(); + mCountOfDeprovisionFail += param.getCountOfDeprovisionFail(); + mTotalServiceUptimeSec += param.getTotalServiceUptimeSec(); + mTotalBatteryConsumptionPercent += param.getTotalBatteryConsumptionPercent(); + mTotalBatteryChargedTimeSec += param.getTotalBatteryChargedTimeSec(); + } + + public void initializeParams() { + mCountOfSatelliteServiceEnablementsSuccess = 0; + mCountOfSatelliteServiceEnablementsFail = 0; + mCountOfOutgoingDatagramSuccess = 0; + mCountOfOutgoingDatagramFail = 0; + mCountOfIncomingDatagramSuccess = 0; + mCountOfIncomingDatagramFail = 0; + mCountOfDatagramTypeSosSmsSuccess = 0; + mCountOfDatagramTypeSosSmsFail = 0; + mCountOfDatagramTypeLocationSharingSuccess = 0; + mCountOfDatagramTypeLocationSharingFail = 0; + mCountOfProvisionSuccess = 0; + mCountOfProvisionFail = 0; + mCountOfDeprovisionSuccess = 0; + mCountOfDeprovisionFail = 0; + mTotalServiceUptimeSec = 0; + mTotalBatteryConsumptionPercent = 0; + mTotalBatteryChargedTimeSec = 0; + } + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bc1d7674ad7bc0d1e489ab9b47f6f4dd1258f644 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java @@ -0,0 +1,499 @@ +/* + * 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 com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +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.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.AsyncResult; +import android.os.Looper; +import android.os.Message; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; +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.satellite.metrics.ControllerMetricsStats; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DatagramDispatcherTest extends TelephonyTest { + private static final String TAG = "DatagramDispatcherTest"; + private static final int SUB_ID = 0; + private static final int DATAGRAM_TYPE1 = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE; + private static final int DATAGRAM_TYPE2 = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING; + private static final String TEST_MESSAGE = "This is a test datagram message"; + private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1); + + private DatagramDispatcher mDatagramDispatcherUT; + private TestDatagramDispatcher mTestDemoModeDatagramDispatcher; + + @Mock private DatagramController mMockDatagramController; + @Mock private DatagramReceiver mMockDatagramReceiver; + @Mock private SatelliteModemInterface mMockSatelliteModemInterface; + @Mock private ControllerMetricsStats mMockControllerMetricsStats; + + /** Variables required to send datagram in the unit tests. */ + LinkedBlockingQueue mResultListener; + SatelliteDatagram mDatagram; + InOrder mInOrder; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + + replaceInstance(DatagramController.class, "sInstance", null, + mMockDatagramController); + replaceInstance(DatagramReceiver.class, "sInstance", null, + mMockDatagramReceiver); + replaceInstance(SatelliteModemInterface.class, "sInstance", null, + mMockSatelliteModemInterface); + replaceInstance(ControllerMetricsStats.class, "sInstance", null, + mMockControllerMetricsStats); + + mDatagramDispatcherUT = DatagramDispatcher.make(mContext, Looper.myLooper(), + mMockDatagramController); + mTestDemoModeDatagramDispatcher = new TestDatagramDispatcher(mContext, Looper.myLooper(), + mMockDatagramController); + + mResultListener = new LinkedBlockingQueue<>(1); + mDatagram = new SatelliteDatagram(TEST_MESSAGE.getBytes()); + mInOrder = inOrder(mMockDatagramController); + when(mMockDatagramController.isPollingInIdleState()).thenReturn(true); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + mDatagramDispatcherUT.destroy(); + mDatagramDispatcherUT = null; + mTestDemoModeDatagramDispatcher = null; + mResultListener = null; + mDatagram = null; + mInOrder = null; + super.tearDown(); + } + + @Test + public void testSendSatelliteDatagram_usingSatelliteModemInterface_success() throws Exception { + doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[3]; + + mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/, + new AsyncResult(message.obj, null, null)) + .sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class), + anyBoolean(), anyBoolean(), any(Message.class)); + + mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram, + true, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController).isPollingInIdleState(); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + verifyNoMoreInteractions(mMockDatagramController); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + } + + @Test + public void testSendSatelliteDatagram_usingSatelliteModemInterface_failure() throws Exception { + doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[3]; + + mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/, + new AsyncResult(message.obj, null, + new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_SERVICE_ERROR))) + .sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class), + anyBoolean(), anyBoolean(), any(Message.class)); + + mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram, + true, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController).isPollingInIdleState(); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0), + eq(SatelliteManager.SATELLITE_SERVICE_ERROR)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + verifyNoMoreInteractions(mMockDatagramController); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR); + } + + @Test + public void testSendSatelliteDatagram_usingCommandsInterface_phoneNull() throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {null}); + + mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram, + true, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController).isPollingInIdleState(); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0), + eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + verifyNoMoreInteractions(mMockDatagramController); + + assertThat(mResultListener.peek()) + .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + + @Test + public void testSendSatelliteDatagram_usingCommandsInterface_success() throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/, + new AsyncResult(message.obj, null, null)) + .sendToTarget(); + return null; + }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class), + anyBoolean()); + + mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram, + true, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController).isPollingInIdleState(); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + verifyNoMoreInteractions(mMockDatagramController); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + } + + @Test + public void testSendSatelliteDatagram_usingCommandsInterface_failure() throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/, + new AsyncResult(message.obj, null, + new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_SERVICE_ERROR))) + .sendToTarget(); + return null; + }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class), + anyBoolean()); + + mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram, + true, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController).isPollingInIdleState(); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0), + eq(SatelliteManager.SATELLITE_SERVICE_ERROR)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + verifyNoMoreInteractions(mMockDatagramController); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR); + } + + @Test + public void testSendSatelliteDatagram_DemoMode_Align_Success() throws Exception { + mTestDemoModeDatagramDispatcher.setDemoMode(true); + mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(true); + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/, + new AsyncResult(message.obj, null, null)) + .sendToTarget(); + return null; + }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class), + anyBoolean()); + + mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram, + true, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + mTestDemoModeDatagramDispatcher.setDemoMode(false); + mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false); + } + + @Test + public void testSendSatelliteDatagram_DemoMode_Align_failed() throws Exception { + long previousTimer = mTestDemoModeDatagramDispatcher.getSatelliteAlignedTimeoutDuration(); + mTestDemoModeDatagramDispatcher.setDemoMode(true); + mTestDemoModeDatagramDispatcher.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN); + mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false); + + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mTestDemoModeDatagramDispatcher.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/, + new AsyncResult(message.obj, null, null)) + .sendToTarget(); + return null; + }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class), + anyBoolean()); + + mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram, + true, mResultListener::offer); + + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + processAllFutureMessages(); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), + anyInt(), eq(SatelliteManager.SATELLITE_NOT_REACHABLE)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_NOT_REACHABLE); + mTestDemoModeDatagramDispatcher.setDemoMode(false); + mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false); + mTestDemoModeDatagramDispatcher.setDuration(previousTimer); + } + + @Test + public void testSendSatelliteDatagram_DemoMode_data_type_location_sharing() throws Exception { + mTestDemoModeDatagramDispatcher.setDemoMode(true); + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/, + new AsyncResult(message.obj, null, null)) + .sendToTarget(); + return null; + }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class), + anyBoolean()); + + mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram, + true, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + + mInOrder.verify(mMockDatagramController) + .updateSendStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + + mTestDemoModeDatagramDispatcher.setDemoMode(false); + mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false); + } + + @Test + public void testSatelliteModemBusy_modemPollingDatagram_sendingDelayed() { + when(mMockDatagramController.isPollingInIdleState()).thenReturn(false); + + mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram, + true, mResultListener::offer); + processAllMessages(); + // As modem is busy receiving datagrams, sending datagram did not proceed further. + mInOrder.verify(mMockDatagramController).isPollingInIdleState(); + verifyNoMoreInteractions(mMockDatagramController); + } + + @Test + public void testOnSatelliteModemStateChanged_modemStateListening() { + mDatagramDispatcherUT.onSatelliteModemStateChanged( + SatelliteManager.SATELLITE_MODEM_STATE_LISTENING); + processAllMessages(); + verifyNoMoreInteractions(mMockDatagramController); + } + + @Test + public void testOnSatelliteModemStateChanged_modemStateOff_modemSendingDatagrams() { + mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram, + true, mResultListener::offer); + + mDatagramDispatcherUT.onSatelliteModemStateChanged( + SatelliteManager.SATELLITE_MODEM_STATE_OFF); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateSendStatus(anyInt(), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), + eq(1), eq(SatelliteManager.SATELLITE_REQUEST_ABORTED)); + mInOrder.verify(mMockDatagramController) + .updateSendStatus(anyInt(), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + } + + @Test + public void testOnSatelliteModemStateChanged_modemStateOff_modemNotSendingDatagrams() { + mDatagramDispatcherUT.onSatelliteModemStateChanged( + SatelliteManager.SATELLITE_MODEM_STATE_OFF); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateSendStatus(anyInt(), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + } + + private static class TestDatagramDispatcher extends DatagramDispatcher { + private long mLong = SATELLITE_ALIGN_TIMEOUT; + + TestDatagramDispatcher(@NonNull Context context, @NonNull Looper looper, + @NonNull DatagramController datagramController) { + super(context, looper, datagramController); + } + + @Override + protected void setDemoMode(boolean isDemoMode) { + super.setDemoMode(isDemoMode); + } + + @Override + protected void onDeviceAlignedWithSatellite(boolean isAligned) { + super.onDeviceAlignedWithSatellite(isAligned); + } + + @Override + protected long getSatelliteAlignedTimeoutDuration() { + return mLong; + } + + public void setDuration(long duration) { + mLong = duration; + } + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1c3777dc30f07c56d9d200803c95d4a605fa0547 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java @@ -0,0 +1,507 @@ +/* + * 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 com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT; + +import android.annotation.NonNull; +import android.content.Context; +import android.provider.Telephony; +import android.telephony.satellite.ISatelliteDatagramCallback; +import android.test.mock.MockContentResolver; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyInt; +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.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.os.AsyncResult; +import android.os.Looper; +import android.os.Message; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; +import android.util.Pair; + +import com.android.internal.telephony.IVoidConsumer; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DatagramReceiverTest extends TelephonyTest { + private static final String TAG = "DatagramReceiverTest"; + private static final int SUB_ID = 0; + private static final String TEST_MESSAGE = "This is a test datagram message"; + private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1); + + private DatagramReceiver mDatagramReceiverUT; + private DatagramReceiver.SatelliteDatagramListenerHandler mSatelliteDatagramListenerHandler; + private TestDatagramReceiver mTestDemoModeDatagramReceiver; + + @Mock private SatelliteController mMockSatelliteController; + @Mock private DatagramController mMockDatagramController; + @Mock private SatelliteModemInterface mMockSatelliteModemInterface; + @Mock private ControllerMetricsStats mMockControllerMetricsStats; + + /** Variables required to receive datagrams in the unit tests. */ + LinkedBlockingQueue mResultListener; + SatelliteDatagram mDatagram; + InOrder mInOrder; + private FakeSatelliteProvider mFakeSatelliteProvider; + private MockContentResolver mMockContentResolver; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + + // Setup mock satellite provider DB. + mFakeSatelliteProvider = new FakeSatelliteProvider(); + mMockContentResolver = new MockContentResolver(); + mMockContentResolver.addProvider( + Telephony.SatelliteDatagrams.PROVIDER_NAME, mFakeSatelliteProvider); + doReturn(mMockContentResolver).when(mContext).getContentResolver(); + + replaceInstance(SatelliteController.class, "sInstance", null, mMockSatelliteController); + replaceInstance(DatagramController.class, "sInstance", null, + mMockDatagramController); + replaceInstance(SatelliteModemInterface.class, "sInstance", null, + mMockSatelliteModemInterface); + replaceInstance(ControllerMetricsStats.class, "sInstance", null, + mMockControllerMetricsStats); + + mDatagramReceiverUT = DatagramReceiver.make(mContext, Looper.myLooper(), + mMockDatagramController); + mTestDemoModeDatagramReceiver = new TestDatagramReceiver(mContext, Looper.myLooper(), + mMockDatagramController); + mSatelliteDatagramListenerHandler = new DatagramReceiver.SatelliteDatagramListenerHandler( + Looper.myLooper(), SUB_ID); + + mResultListener = new LinkedBlockingQueue<>(1); + mDatagram = new SatelliteDatagram(TEST_MESSAGE.getBytes()); + mInOrder = inOrder(mMockDatagramController); + + when(mMockDatagramController.isSendingInIdleState()).thenReturn(true); + when(mMockDatagramController.isPollingInIdleState()).thenReturn(true); + processAllMessages(); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + mFakeSatelliteProvider.shutdown(); + mDatagramReceiverUT.destroy(); + mDatagramReceiverUT = null; + mTestDemoModeDatagramReceiver = null; + mResultListener = null; + mDatagram = null; + mInOrder = null; + super.tearDown(); + } + + @Test + public void testPollPendingSatelliteDatagrams_usingSatelliteModemInterface_success() + throws Exception { + doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/, + new AsyncResult(message.obj, null, null)) + .sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class)); + + mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + } + + @Test + public void testPollPendingSatelliteDatagrams_usingSatelliteModemInterface_failure() + throws Exception { + doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/, + new AsyncResult(message.obj, null, + new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_SERVICE_ERROR))) + .sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class)); + + mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), + eq(0), eq(SatelliteManager.SATELLITE_SERVICE_ERROR)); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR); + } + + @Test + public void testPollPendingSatelliteDatagrams_usingCommandsInterface_phoneNull() + throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {null}); + + mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), + eq(0), eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)); + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + + assertThat(mResultListener.peek()) + .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + + @Test + public void testPollPendingSatelliteDatagrams_usingCommandsInterface_success() + throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/, + new AsyncResult(message.obj, null, null)) + .sendToTarget(); + return null; + }).when(mPhone).pollPendingSatelliteDatagrams(any(Message.class)); + + mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + } + + @Test + public void testPollPendingSatelliteDatagrams_usingCommandsInterface_failure() + throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + + mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/, + new AsyncResult(message.obj, null, + new SatelliteManager.SatelliteException( + SatelliteManager.SATELLITE_SERVICE_ERROR))) + .sendToTarget(); + return null; + }).when(mPhone).pollPendingSatelliteDatagrams(any(Message.class)); + + mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), + eq(0), eq(SatelliteManager.SATELLITE_SERVICE_ERROR)); + + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR); + } + + @Test + public void testSatelliteDatagramReceived_receiveNone() { + mSatelliteDatagramListenerHandler.obtainMessage(1 /*EVENT_SATELLITE_DATAGRAM_RECEIVED*/, + new AsyncResult(null, new Pair<>(null, 0), null)) + .sendToTarget(); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + } + + @Test + public void testSatelliteDatagramReceived_success_zeroPendingCount() { + mSatelliteDatagramListenerHandler.obtainMessage(1 /*EVENT_SATELLITE_DATAGRAM_RECEIVED*/, + new AsyncResult(null, new Pair<>(mDatagram, 0), null)) + .sendToTarget(); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + } + + @Test + public void testSatelliteDatagramReceived_success_nonZeroPendingCount() { + mSatelliteDatagramListenerHandler.obtainMessage(1 /*EVENT_SATELLITE_DATAGRAM_RECEIVED*/, + new AsyncResult(null, new Pair<>(mDatagram, 10), null)) + .sendToTarget(); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS), + eq(10), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + } + + @Test + public void testPollPendingSatelliteDatagrams_DemoMode_Align_succeed() throws Exception { + // Checks invalid case only as SatelliteController does not exist in unit test + mTestDemoModeDatagramReceiver.setDemoMode(true); + mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(true); + when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram); + mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + processAllMessages(); + verify(mMockDatagramController, times(1)).getDemoModeDatagram(); + verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), + anyInt(), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), + anyInt(), + eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)); + verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + anyInt(), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + assertThat(mResultListener.peek()) + .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + } + + @Test + public void testPollPendingSatelliteDatagrams_DemoMode_Align_failed() throws Exception { + // Checks invalid case only as SatelliteController does not exist in unit test + long previousTimer = mTestDemoModeDatagramReceiver.getSatelliteAlignedTimeoutDuration(); + mTestDemoModeDatagramReceiver.setDemoMode(true); + mTestDemoModeDatagramReceiver.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN); + mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(false); + when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram); + mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + processAllMessages(); + verify(mMockDatagramController, never()).getDemoModeDatagram(); + verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), + anyInt(), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + processAllFutureMessages(); + verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), + anyInt(), + eq(SatelliteManager.SATELLITE_NOT_REACHABLE)); + verify(mMockDatagramController) + .updateReceiveStatus(eq(SUB_ID), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + anyInt(), + eq(SatelliteManager.SATELLITE_ERROR_NONE)); + assertThat(mResultListener.peek()) + .isEqualTo(SatelliteManager.SATELLITE_NOT_REACHABLE); + + mTestDemoModeDatagramReceiver.setDemoMode(false); + mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(false); + mTestDemoModeDatagramReceiver.setDuration(previousTimer); + } + + @Test + public void testSatelliteModemBusy_modemSendingDatagram_pollingFailure() { + when(mMockDatagramController.isSendingInIdleState()).thenReturn(false); + + mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + processAllMessages(); + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_MODEM_BUSY); + } + + @Test + public void testSatelliteModemBusy_modemPollingDatagrams_pollingFailure() { + when(mMockDatagramController.isSendingInIdleState()).thenReturn(false); + when(mMockDatagramController.isPollingInIdleState()).thenReturn(true); + + mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer); + processAllMessages(); + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_MODEM_BUSY); + } + + @Test + public void testOnSatelliteModemStateChanged_modemStateIdle() { + mDatagramReceiverUT.onSatelliteModemStateChanged( + SatelliteManager.SATELLITE_MODEM_STATE_IDLE); + processAllMessages(); + verifyNoMoreInteractions(mMockDatagramController); + } + + @Test + public void testOnSatelliteModemStateChanged_modemStateOff_modemReceivingDatagrams() { + when(mMockDatagramController.isReceivingDatagrams()).thenReturn(true); + when(mMockDatagramController.getReceivePendingCount()).thenReturn(10); + + mDatagramReceiverUT.onSatelliteModemStateChanged( + SatelliteManager.SATELLITE_MODEM_STATE_OFF); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(anyInt(), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), + eq(10), eq(SatelliteManager.SATELLITE_REQUEST_ABORTED)); + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(anyInt(), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + } + + @Test + public void testOnSatelliteModemStateChanged_modemStateOff_modemNotReceivingDatagrams() { + when(mMockDatagramController.isReceivingDatagrams()).thenReturn(false); + + mDatagramReceiverUT.onSatelliteModemStateChanged( + SatelliteManager.SATELLITE_MODEM_STATE_OFF); + + processAllMessages(); + + mInOrder.verify(mMockDatagramController) + .updateReceiveStatus(anyInt(), + eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), + eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE)); + } + + @Test + public void testRegisterForSatelliteDatagram_satelliteNotSupported() { + when(mMockSatelliteController.isSatelliteSupported()).thenReturn(false); + + ISatelliteDatagramCallback callback = new ISatelliteDatagramCallback() { + @Override + public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram, + int pendingCount, IVoidConsumer callback) throws RemoteException { + logd("onSatelliteDatagramReceived"); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + + assertThat(mDatagramReceiverUT.registerForSatelliteDatagram(SUB_ID, callback)) + .isEqualTo(SatelliteManager.SATELLITE_NOT_SUPPORTED); + } + + private static class TestDatagramReceiver extends DatagramReceiver { + private long mLong = SATELLITE_ALIGN_TIMEOUT; + + TestDatagramReceiver(@NonNull Context context, @NonNull Looper looper, + @NonNull DatagramController datagramController) { + super(context, looper, datagramController); + } + + @Override + protected void setDemoMode(boolean isDemoMode) { + super.setDemoMode(isDemoMode); + } + + @Override + protected void onDeviceAlignedWithSatellite(boolean isAligned) { + super.onDeviceAlignedWithSatellite(isAligned); + } + + @Override + protected long getSatelliteAlignedTimeoutDuration() { + return mLong; + } + + public void setDuration(long duration) { + mLong = duration; + } + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/FakeSatelliteProvider.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/FakeSatelliteProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..b0c6a819d39b97ac1adacab03e8f6f0f5743e383 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/FakeSatelliteProvider.java @@ -0,0 +1,103 @@ +/* + * 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.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Telephony; +import android.test.mock.MockContentProvider; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; + +public class FakeSatelliteProvider extends MockContentProvider { + private static final String TAG = "FakeSatelliteProvider"; + + private InMemorySatelliteProviderDbHelper mDbHelper = new InMemorySatelliteProviderDbHelper(); + + private class InMemorySatelliteProviderDbHelper extends SQLiteOpenHelper { + + InMemorySatelliteProviderDbHelper() { + super(InstrumentationRegistry.getTargetContext(), + null, // db file name is null for in-memory db + null, // CursorFactory is null by default + 1); // db version is no-op for tests + Log.d(TAG, "InMemorySatelliteProviderDbHelper creating in-memory database"); + } + public static String getStringForDatagramTableCreation(String tableName) { + return "CREATE TABLE " + tableName + "(" + + Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID + + " INTEGER PRIMARY KEY," + + Telephony.SatelliteDatagrams.COLUMN_DATAGRAM + " BLOB DEFAULT ''" + + ");"; + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.d(TAG, "InMemoryTelephonyProviderDbHelper onCreate:" + + " creating satellite incoming datagram table"); + db.execSQL(getStringForDatagramTableCreation(Telephony.SatelliteDatagrams.TABLE_NAME)); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // Do nothing. + } + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + Log.d(TAG, "insert. values=" + values); + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + long id = db.insert(Telephony.SatelliteDatagrams.TABLE_NAME, null, values); + return ContentUris.withAppendedId(Telephony.SatelliteDatagrams.CONTENT_URI, id); + } + + @Override + public synchronized int delete(Uri url, String where, String[] whereArgs) { + return mDbHelper.getWritableDatabase() + .delete(Telephony.SatelliteDatagrams.TABLE_NAME, where, whereArgs); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return mDbHelper.getReadableDatabase().query(Telephony.SatelliteDatagrams.TABLE_NAME, + projection, selection, selectionArgs, null, null, sortOrder); + } + + @Override + public Bundle call(String method, String request, Bundle args) { + return null; + } + + @Override + public final int update(Uri uri, ContentValues values, String where, String[] selectionArgs) { + // Do nothing. + return 0; + } + + @Override + public void shutdown() { + mDbHelper.close(); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteCapabilitiesTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteCapabilitiesTest.java new file mode 100644 index 0000000000000000000000000000000000000000..29473c9787b06ca1bbd1ceba617f67efde7effca --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteCapabilitiesTest.java @@ -0,0 +1,134 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; +import android.telephony.satellite.AntennaDirection; +import android.telephony.satellite.AntennaPosition; +import android.telephony.satellite.SatelliteCapabilities; +import android.telephony.satellite.SatelliteManager; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class SatelliteCapabilitiesTest { + + private AntennaDirection mAntennaDirection = new AntennaDirection(1,1,1); + + @Test + public void testParcel() { + Set satelliteRadioTechnologies = new HashSet<>(); + satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN); + satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN); + + Map antennaPositionMap = new HashMap<>(); + AntennaPosition antennaPosition = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT); + antennaPositionMap.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition); + + SatelliteCapabilities capabilities = new SatelliteCapabilities(satelliteRadioTechnologies, + true, 10, antennaPositionMap); + + Parcel p = Parcel.obtain(); + capabilities.writeToParcel(p, 0); + p.setDataPosition(0); + + SatelliteCapabilities fromParcel = SatelliteCapabilities.CREATOR.createFromParcel(p); + assertThat(capabilities).isEqualTo(fromParcel); + } + + @Test + public void testParcel_emptySatelliteRadioTechnologies() { + Set satelliteRadioTechnologies = new HashSet<>(); + + Map antennaPositionMap = new HashMap<>(); + AntennaPosition antennaPosition1 = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT); + AntennaPosition antennaPosition2 = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT); + antennaPositionMap.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition1); + antennaPositionMap.put(SatelliteManager.DISPLAY_MODE_CLOSED, antennaPosition2); + + SatelliteCapabilities capabilities = new SatelliteCapabilities(satelliteRadioTechnologies, + false, 100, antennaPositionMap); + + Parcel p = Parcel.obtain(); + capabilities.writeToParcel(p, 0); + p.setDataPosition(0); + + SatelliteCapabilities fromParcel = SatelliteCapabilities.CREATOR.createFromParcel(p); + assertThat(capabilities).isEqualTo(fromParcel); + } + + + @Test + public void testParcel_emptyAntennaPosition() { + Set satelliteRadioTechnologies = new HashSet<>(); + satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN); + + SatelliteCapabilities capabilities = new SatelliteCapabilities(satelliteRadioTechnologies, + true, 0, new HashMap<>()); + + Parcel p = Parcel.obtain(); + capabilities.writeToParcel(p, 0); + p.setDataPosition(0); + + SatelliteCapabilities fromParcel = SatelliteCapabilities.CREATOR.createFromParcel(p); + assertThat(capabilities).isEqualTo(fromParcel); + } + + @Test + public void testEquals() { + Set satelliteRadioTechnologies = new HashSet<>(); + satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN); + satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN); + + AntennaPosition antennaPosition1 = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT); + AntennaPosition antennaPosition2 = new AntennaPosition(mAntennaDirection, + SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT); + + Map antennaPositionMap1 = new HashMap<>(); + antennaPositionMap1.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition1); + antennaPositionMap1.put(SatelliteManager.DISPLAY_MODE_CLOSED, antennaPosition2); + + SatelliteCapabilities satelliteCapabilities1 = + new SatelliteCapabilities(satelliteRadioTechnologies, true, 10, + antennaPositionMap1); + SatelliteCapabilities satelliteCapabilities2 = + new SatelliteCapabilities(satelliteRadioTechnologies, true, 10, + antennaPositionMap1); + assertEquals(satelliteCapabilities1, satelliteCapabilities2); + + Map antennaPositionMap2 = new HashMap<>(); + antennaPositionMap2.put(SatelliteManager.DISPLAY_MODE_CLOSED, antennaPosition1); + antennaPositionMap2.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition2); + satelliteCapabilities2 = + new SatelliteCapabilities(satelliteRadioTechnologies, true, 10, + antennaPositionMap2); + assertNotEquals(satelliteCapabilities1, satelliteCapabilities2); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f6ed2e24dfe18dcf660434857e3ab190e599443f --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java @@ -0,0 +1,2025 @@ +/* + * 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.satellite.SatelliteManager.KEY_DEMO_MODE_ENABLED; +import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_CAPABILITIES; +import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED; +import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_ENABLED; +import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_NEXT_VISIBILITY; +import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_PROVISIONED; +import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_SUPPORTED; +import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN; +import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN; +import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY; +import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR; +import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR_NONE; +import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_ARGUMENTS; +import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_MODEM_STATE; +import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; +import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF; +import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE; +import static android.telephony.satellite.SatelliteManager.SATELLITE_NOT_AUTHORIZED; +import static android.telephony.satellite.SatelliteManager.SATELLITE_NOT_SUPPORTED; +import static android.telephony.satellite.SatelliteManager.SATELLITE_NO_RESOURCES; +import static android.telephony.satellite.SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE; +import static android.telephony.satellite.SatelliteManager.SATELLITE_REQUEST_IN_PROGRESS; +import static android.telephony.satellite.SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED; +import static android.telephony.satellite.SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS; + +import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_FALSE; +import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_TRUE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +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.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +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 static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncResult; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.ICancellationSignal; +import android.os.Looper; +import android.os.Message; +import android.os.ResultReceiver; +import android.telephony.Rlog; +import android.telephony.satellite.ISatelliteDatagramCallback; +import android.telephony.satellite.ISatelliteProvisionStateCallback; +import android.telephony.satellite.ISatelliteStateCallback; +import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; +import android.telephony.satellite.SatelliteCapabilities; +import android.telephony.satellite.SatelliteDatagram; +import android.telephony.satellite.SatelliteManager; +import android.telephony.satellite.SatelliteManager.SatelliteException; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.IIntegerConsumer; +import com.android.internal.telephony.IVoidConsumer; +import com.android.internal.telephony.Phone; +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 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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class SatelliteControllerTest extends TelephonyTest { + private static final String TAG = "SatelliteControllerTest"; + private static final long TIMEOUT = 500; + private static final int SUB_ID = 0; + 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 TestSatelliteController mSatelliteControllerUT; + private TestSharedPreferences mSharedPreferences; + + @Mock private DatagramController mMockDatagramController; + @Mock private SatelliteModemInterface mMockSatelliteModemInterface; + @Mock private SatelliteSessionController mMockSatelliteSessionController; + @Mock private PointingAppController mMockPointingAppController; + @Mock private ControllerMetricsStats mMockControllerMetricsStats; + @Mock private ProvisionMetricsStats mMockProvisionMetricsStats; + @Mock private SessionMetricsStats mMockSessionMetricsStats; + private List mIIntegerConsumerResults = new ArrayList<>(); + @Mock private ISatelliteTransmissionUpdateCallback mStartTransmissionUpdateCallback; + @Mock private ISatelliteTransmissionUpdateCallback mStopTransmissionUpdateCallback; + private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0); + private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + logd("mIIntegerConsumer: result=" + result); + mIIntegerConsumerResults.add(result); + try { + mIIntegerConsumerSemaphore.release(); + } catch (Exception ex) { + loge("mIIntegerConsumer: Got exception in releasing semaphore, ex=" + ex); + } + } + }; + + private boolean mIsSatelliteServiceSupported = true; + private boolean mIsPointingRequired = true; + private Set mSupportedRadioTechnologies = new HashSet<>(Arrays.asList( + NT_RADIO_TECHNOLOGY_NR_NTN, + NT_RADIO_TECHNOLOGY_EMTC_NTN, + NT_RADIO_TECHNOLOGY_PROPRIETARY)); + private SatelliteCapabilities mSatelliteCapabilities = new SatelliteCapabilities( + mSupportedRadioTechnologies, mIsPointingRequired, MAX_BYTES_PER_OUT_GOING_DATAGRAM, + new HashMap<>()); + private Semaphore mSatelliteCapabilitiesSemaphore = new Semaphore(0); + private SatelliteCapabilities mQueriedSatelliteCapabilities = null; + private int mQueriedSatelliteCapabilitiesResultCode = SATELLITE_ERROR_NONE; + private ResultReceiver mSatelliteCapabilitiesReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mQueriedSatelliteCapabilitiesResultCode = resultCode; + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SATELLITE_CAPABILITIES)) { + mQueriedSatelliteCapabilities = resultData.getParcelable( + KEY_SATELLITE_CAPABILITIES, SatelliteCapabilities.class); + } else { + loge("KEY_SATELLITE_SUPPORTED does not exist."); + mQueriedSatelliteCapabilities = null; + } + } else { + logd("mSatelliteSupportReceiver: resultCode=" + resultCode); + mQueriedSatelliteCapabilities = null; + } + try { + mSatelliteCapabilitiesSemaphore.release(); + } catch (Exception ex) { + loge("mSatelliteSupportReceiver: Got exception in releasing semaphore, ex=" + ex); + } + } + }; + + private boolean mQueriedSatelliteSupported = false; + private int mQueriedSatelliteSupportedResultCode = SATELLITE_ERROR_NONE; + private Semaphore mSatelliteSupportSemaphore = new Semaphore(0); + private ResultReceiver mSatelliteSupportReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mQueriedSatelliteSupportedResultCode = resultCode; + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SATELLITE_SUPPORTED)) { + mQueriedSatelliteSupported = resultData.getBoolean(KEY_SATELLITE_SUPPORTED); + } else { + loge("KEY_SATELLITE_SUPPORTED does not exist."); + mQueriedSatelliteSupported = false; + } + } else { + logd("mSatelliteSupportReceiver: resultCode=" + resultCode); + mQueriedSatelliteSupported = false; + } + try { + mSatelliteSupportSemaphore.release(); + } catch (Exception ex) { + loge("mSatelliteSupportReceiver: Got exception in releasing semaphore, ex=" + ex); + } + } + }; + + private boolean mQueriedIsSatelliteEnabled = false; + private int mQueriedIsSatelliteEnabledResultCode = SATELLITE_ERROR_NONE; + private Semaphore mIsSatelliteEnabledSemaphore = new Semaphore(0); + private ResultReceiver mIsSatelliteEnabledReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mQueriedIsSatelliteEnabledResultCode = resultCode; + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SATELLITE_ENABLED)) { + mQueriedIsSatelliteEnabled = resultData.getBoolean(KEY_SATELLITE_ENABLED); + } else { + loge("KEY_SATELLITE_ENABLED does not exist."); + mQueriedIsSatelliteEnabled = false; + } + } else { + logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode); + mQueriedIsSatelliteEnabled = false; + } + try { + mIsSatelliteEnabledSemaphore.release(); + } catch (Exception ex) { + loge("mIsSatelliteEnableReceiver: Got exception in releasing semaphore, ex=" + ex); + } + } + }; + + private boolean mQueriedIsDemoModeEnabled = false; + private int mQueriedIsDemoModeEnabledResultCode = SATELLITE_ERROR_NONE; + private Semaphore mIsDemoModeEnabledSemaphore = new Semaphore(0); + private ResultReceiver mIsDemoModeEnabledReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mQueriedIsDemoModeEnabledResultCode = resultCode; + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_DEMO_MODE_ENABLED)) { + mQueriedIsDemoModeEnabled = resultData.getBoolean(KEY_DEMO_MODE_ENABLED); + } else { + loge("KEY_DEMO_MODE_ENABLED does not exist."); + mQueriedIsDemoModeEnabled = false; + } + } else { + logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode); + mQueriedIsDemoModeEnabled = false; + } + try { + mIsDemoModeEnabledSemaphore.release(); + } catch (Exception ex) { + loge("mIsDemoModeEnabledReceiver: Got exception in releasing semaphore, ex=" + ex); + } + } + }; + + private boolean mQueriedIsSatelliteProvisioned = false; + private int mQueriedIsSatelliteProvisionedResultCode = SATELLITE_ERROR_NONE; + private Semaphore mIsSatelliteProvisionedSemaphore = new Semaphore(0); + private ResultReceiver mIsSatelliteProvisionedReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mQueriedIsSatelliteProvisionedResultCode = resultCode; + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) { + mQueriedIsSatelliteProvisioned = + resultData.getBoolean(KEY_SATELLITE_PROVISIONED); + } else { + loge("KEY_SATELLITE_PROVISIONED does not exist."); + mQueriedIsSatelliteProvisioned = false; + } + } else { + mQueriedIsSatelliteProvisioned = false; + } + try { + mIsSatelliteProvisionedSemaphore.release(); + } catch (Exception ex) { + loge("mIsSatelliteProvisionedReceiver: Got exception in releasing semaphore ex=" + + ex); + } + } + }; + + private boolean mQueriedSatelliteAllowed = false; + private int mQueriedSatelliteAllowedResultCode = SATELLITE_ERROR_NONE; + private Semaphore mSatelliteAllowedSemaphore = new Semaphore(0); + private ResultReceiver mSatelliteAllowedReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mQueriedSatelliteAllowedResultCode = resultCode; + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) { + mQueriedSatelliteAllowed = resultData.getBoolean( + KEY_SATELLITE_COMMUNICATION_ALLOWED); + } else { + loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist."); + mQueriedSatelliteAllowed = false; + } + } else { + logd("mSatelliteSupportReceiver: resultCode=" + resultCode); + mQueriedSatelliteAllowed = false; + } + try { + mSatelliteAllowedSemaphore.release(); + } catch (Exception ex) { + loge("mSatelliteAllowedReceiver: Got exception in releasing semaphore, ex=" + ex); + } + } + }; + + private int mQueriedSatelliteVisibilityTime = -1; + private int mSatelliteNextVisibilityTime = 3600; + private int mQueriedSatelliteVisibilityTimeResultCode = SATELLITE_ERROR_NONE; + private Semaphore mSatelliteVisibilityTimeSemaphore = new Semaphore(0); + private ResultReceiver mSatelliteVisibilityTimeReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mQueriedSatelliteVisibilityTimeResultCode = resultCode; + if (resultCode == SATELLITE_ERROR_NONE) { + if (resultData.containsKey(KEY_SATELLITE_NEXT_VISIBILITY)) { + mQueriedSatelliteVisibilityTime = resultData.getInt( + KEY_SATELLITE_NEXT_VISIBILITY); + } else { + loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist."); + mQueriedSatelliteVisibilityTime = -1; + } + } else { + logd("mSatelliteSupportReceiver: resultCode=" + resultCode); + mQueriedSatelliteVisibilityTime = -1; + } + try { + mSatelliteVisibilityTimeSemaphore.release(); + } catch (Exception ex) { + loge("mSatelliteAllowedReceiver: Got exception in releasing semaphore, ex=" + ex); + } + } + }; + + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + + replaceInstance(DatagramController.class, "sInstance", null, + mMockDatagramController); + replaceInstance(SatelliteModemInterface.class, "sInstance", null, + mMockSatelliteModemInterface); + replaceInstance(SatelliteSessionController.class, "sInstance", null, + mMockSatelliteSessionController); + replaceInstance(PointingAppController.class, "sInstance", null, + mMockPointingAppController); + replaceInstance(ControllerMetricsStats.class, "sInstance", null, + mMockControllerMetricsStats); + replaceInstance(ProvisionMetricsStats.class, "sInstance", null, + mMockProvisionMetricsStats); + replaceInstance(SessionMetricsStats.class, "sInstance", null, + mMockSessionMetricsStats); + + mSharedPreferences = new TestSharedPreferences(); + when(mContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences); + doReturn(mIsSatelliteServiceSupported) + .when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + setUpResponseForRequestSatelliteCapabilities( + mSatelliteCapabilities, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE); + doNothing().when(mMockDatagramController).setDemoMode(anyBoolean()); + doNothing().when(mMockSatelliteSessionController) + .onSatelliteEnabledStateChanged(anyBoolean()); + doNothing().when(mMockSatelliteSessionController).setDemoMode(anyBoolean()); + doNothing().when(mMockControllerMetricsStats).onSatelliteEnabled(); + doNothing().when(mMockControllerMetricsStats).reportServiceEnablementSuccessCount(); + doNothing().when(mMockControllerMetricsStats).reportServiceEnablementFailCount(); + doReturn(mMockSessionMetricsStats) + .when(mMockSessionMetricsStats).setInitializationResult(anyInt()); + doReturn(mMockSessionMetricsStats) + .when(mMockSessionMetricsStats).setRadioTechnology(anyInt()); + doNothing().when(mMockSessionMetricsStats).reportSessionMetrics(); + + doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats) + .setResultCode(anyInt()); + doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats) + .setIsProvisionRequest(eq(false)); + doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics(); + doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt()); + mSatelliteControllerUT = new TestSatelliteController(mContext, Looper.myLooper()); + verify(mMockSatelliteModemInterface).registerForSatelliteProvisionStateChanged( + any(Handler.class), + eq(26) /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */, + eq(null)); + verify(mMockSatelliteModemInterface).registerForPendingDatagrams( + any(Handler.class), + eq(27) /* EVENT_PENDING_DATAGRAMS */, + eq(null)); + verify(mMockSatelliteModemInterface).registerForSatelliteModemStateChanged( + any(Handler.class), + eq(28) /* EVENT_SATELLITE_MODEM_STATE_CHANGED */, + eq(null)); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + mSatelliteControllerUT = null; + super.tearDown(); + } + + @Test + public void testRequestIsSatelliteCommunicationAllowedForCurrentLocation() { + mSatelliteAllowedSemaphore.drainPermits(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID, + mSatelliteAllowedReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteAllowedResultCode); + + resetSatelliteControllerUT(); + mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID, + mSatelliteAllowedReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID, + mSatelliteAllowedReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1)); + assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteAllowedResultCode); + assertTrue(mQueriedSatelliteAllowed); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID, + mSatelliteAllowedReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation( + SATELLITE_INVALID_MODEM_STATE); + mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID, + mSatelliteAllowedReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1)); + assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteAllowedResultCode); + } + + @Test + public void testRequestTimeForNextSatelliteVisibility() { + mSatelliteVisibilityTimeSemaphore.drainPermits(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID, + mSatelliteVisibilityTimeReceiver); + processAllMessages(); + assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteVisibilityTimeResultCode); + + resetSatelliteControllerUT(); + mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID, + mSatelliteVisibilityTimeReceiver); + processAllMessages(); + assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime, + SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID, + mSatelliteVisibilityTimeReceiver); + processAllMessages(); + assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime, + SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID, + mSatelliteVisibilityTimeReceiver); + processAllMessages(); + assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1)); + assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, mQueriedSatelliteVisibilityTimeResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime, + SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID, + mSatelliteVisibilityTimeReceiver); + processAllMessages(); + assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1)); + assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteVisibilityTimeResultCode); + assertEquals(mSatelliteNextVisibilityTime, mQueriedSatelliteVisibilityTime); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpNullResponseForRequestTimeForNextSatelliteVisibility( + SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID, + mSatelliteVisibilityTimeReceiver); + processAllMessages(); + assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpNullResponseForRequestTimeForNextSatelliteVisibility( + SATELLITE_INVALID_MODEM_STATE); + mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID, + mSatelliteVisibilityTimeReceiver); + processAllMessages(); + assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1)); + assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteVisibilityTimeResultCode); + } + + @Test + public void testRequestSatelliteEnabled() { + mIsSatelliteEnabledSemaphore.drainPermits(); + + // Fail to enable satellite when SatelliteController is not fully loaded yet. + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + + // Fail to enable satellite when the device does not support satellite. + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0)); + + // Fail to enable satellite when the device is not provisioned yet. + mIIntegerConsumerResults.clear(); + resetSatelliteControllerUT(); + verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(false)); + verify(mMockSatelliteSessionController, times(1)).setDemoMode(eq(false)); + verify(mMockDatagramController, times(1)).setDemoMode(eq(false)); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0)); + + sendProvisionedStateChangedEvent(true, null); + processAllMessages(); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + + // Successfully disable satellite when radio is turned off. + mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false; + setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE); + setRadioPower(false); + 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(mMockControllerMetricsStats, times(1)).onSatelliteDisabled(); + + // Fail to enable satellite when radio is off. + mIIntegerConsumerResults.clear(); + setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + // Radio is not on, can not enable satellite + assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0)); + + setRadioPower(true); + processAllMessages(); + verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); + + // Fail to enable satellite with an error response from modem when radio is on. + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false; + setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_INVALID_MODEM_STATE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0)); + verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); + verify(mMockPointingAppController, never()).startPointingUI(anyBoolean()); + assertFalse(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled); + verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementFailCount(); + + // Successfully enable satellite when radio is on. + 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(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(); + + // Successfully enable satellite when it is already enabled. + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verifySatelliteEnabled(true, SATELLITE_ERROR_NONE); + + // Fail to enable satellite with a different demo mode when it is already enabled. + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, true, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0)); + verifySatelliteEnabled(true, SATELLITE_ERROR_NONE); + + // Disable satellite. + mIIntegerConsumerResults.clear(); + setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); + + // Disable satellite when satellite is already disabled. + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); + + // Disable satellite with a different demo mode when satellite is already disabled. + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, true, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); + + // Send a second request while the first request in progress + mIIntegerConsumerResults.clear(); + setUpNoResponseForRequestSatelliteEnabled(true, false); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertFalse(waitForIIntegerConsumerResult(1)); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_REQUEST_IN_PROGRESS, (long) mIIntegerConsumerResults.get(0)); + + mIIntegerConsumerResults.clear(); + resetSatelliteControllerUTToSupportedAndProvisionedState(); + // Should receive callback for the above request when satellite modem is turned off. + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0)); + + // Move to satellite-disabling in progress. + setUpNoResponseForRequestSatelliteEnabled(false, false); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer); + processAllMessages(); + assertFalse(waitForIIntegerConsumerResult(1)); + + // Disable is in progress. Thus, a new request to enable satellite will be rejected. + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR, (long) mIIntegerConsumerResults.get(0)); + + mIIntegerConsumerResults.clear(); + resetSatelliteControllerUTToOffAndProvisionedState(); + // Should receive callback for the above request when satellite modem is turned off. + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0)); + + /** + * Make areAllRadiosDisabled return false and move mWaitingForRadioDisabled to true, which + * will lead to no response for requestSatelliteEnabled. + */ + mSatelliteControllerUT.allRadiosDisabled = false; + setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertFalse(waitForIIntegerConsumerResult(1)); + + resetSatelliteControllerUTEnabledState(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer); + processAllMessages(); + // We should receive 2 callbacks for the above 2 requests. + assertTrue(waitForIIntegerConsumerResult(2)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(1)); + + resetSatelliteControllerUTToOffAndProvisionedState(); + + // Repeat the same test as above but with error response from modem for the second request + mSatelliteControllerUT.allRadiosDisabled = false; + setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertFalse(waitForIIntegerConsumerResult(1)); + + resetSatelliteControllerUTEnabledState(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_NO_RESOURCES); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer); + processAllMessages(); + // We should receive 2 callbacks for the above 2 requests. + assertTrue(waitForIIntegerConsumerResult(2)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + assertEquals(SATELLITE_NO_RESOURCES, (long) mIIntegerConsumerResults.get(1)); + mSatelliteControllerUT.allRadiosDisabled = true; + } + + @Test + public void testRequestSatelliteCapabilities() { + mSatelliteCapabilitiesSemaphore.drainPermits(); + mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver); + processAllMessages(); + assertTrue(waitForRequestSatelliteCapabilitiesResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteCapabilitiesResultCode); + + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver); + processAllMessages(); + assertTrue(waitForRequestSatelliteCapabilitiesResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteCapabilitiesResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestSatelliteCapabilities(mSatelliteCapabilities, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver); + processAllMessages(); + assertTrue(waitForRequestSatelliteCapabilitiesResult(1)); + assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteCapabilitiesResultCode); + assertEquals(mSatelliteCapabilities, mQueriedSatelliteCapabilities); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver); + processAllMessages(); + assertTrue(waitForRequestSatelliteCapabilitiesResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteCapabilitiesResultCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_INVALID_MODEM_STATE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver); + processAllMessages(); + assertTrue(waitForRequestSatelliteCapabilitiesResult(1)); + assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteCapabilitiesResultCode); + } + + @Test + public void testStartSatelliteTransmissionUpdates() { + mIIntegerConsumerSemaphore.drainPermits(); + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStartTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStartTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStartTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE); + mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStartTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE); + mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStartTransmissionUpdateCallback); + verify(mMockPointingAppController).registerForSatelliteTransmissionUpdates(anyInt(), + eq(mStartTransmissionUpdateCallback), any()); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verify(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class), + any(Phone.class)); + verify(mMockPointingAppController).setStartedSatelliteTransmissionUpdates(eq(true)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_INVALID_TELEPHONY_STATE); + mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStartTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + verify(mMockPointingAppController).unregisterForSatelliteTransmissionUpdates(anyInt(), + any(), eq(mStartTransmissionUpdateCallback), any(Phone.class)); + verify(mMockPointingAppController).setStartedSatelliteTransmissionUpdates(eq(false)); + } + + @Test + public void testStopSatelliteTransmissionUpdates() { + mIIntegerConsumerSemaphore.drainPermits(); + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStopTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStopTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStopTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE); + mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStopTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE); + mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStopTransmissionUpdateCallback); + verify(mMockPointingAppController).unregisterForSatelliteTransmissionUpdates(anyInt(), + any(), eq(mStopTransmissionUpdateCallback), any(Phone.class)); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verify(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class), + any(Phone.class)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_INVALID_TELEPHONY_STATE); + mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer, + mStopTransmissionUpdateCallback); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + } + + @Test + public void testRequestIsDemoModeEnabled() { + mIsDemoModeEnabledSemaphore.drainPermits(); + resetSatelliteControllerUT(); + mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver); + assertTrue(waitForRequestIsDemoModeEnabledResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode); + assertFalse(mQueriedIsDemoModeEnabled); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver); + assertTrue(waitForRequestIsDemoModeEnabledResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedIsDemoModeEnabledResultCode); + assertFalse(mQueriedIsDemoModeEnabled); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver); + assertTrue(waitForRequestIsDemoModeEnabledResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode); + assertFalse(mQueriedIsDemoModeEnabled); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver); + assertTrue(waitForRequestIsDemoModeEnabledResult(1)); + assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, mQueriedIsDemoModeEnabledResultCode); + assertFalse(mQueriedIsDemoModeEnabled); + + resetSatelliteControllerUT(); + boolean isDemoModeEnabled = mSatelliteControllerUT.isDemoModeEnabled(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver); + assertTrue(waitForRequestIsDemoModeEnabledResult(1)); + assertEquals(SATELLITE_ERROR_NONE, mQueriedIsDemoModeEnabledResultCode); + assertEquals(isDemoModeEnabled, mQueriedIsDemoModeEnabled); + } + + @Test + public void testIsSatelliteEnabled() { + assertFalse(mSatelliteControllerUT.isSatelliteEnabled()); + setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_ERROR_NONE); + mIsSatelliteEnabledSemaphore.drainPermits(); + mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteEnabledResult(1)); + assertEquals(mSatelliteControllerUT.isSatelliteEnabled(), mQueriedIsSatelliteEnabled); + } + + @Test + public void testOnSatelliteServiceConnected() { + verifySatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE); + verifySatelliteEnabled(false, SATELLITE_INVALID_TELEPHONY_STATE); + verifySatelliteProvisioned(false, SATELLITE_INVALID_TELEPHONY_STATE); + + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE); + + mSatelliteControllerUT.onSatelliteServiceConnected(); + processAllMessages(); + + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + } + + @Test + public void testRegisterForSatelliteModemStateChanged() { + ISatelliteStateCallback callback = new ISatelliteStateCallback.Stub() { + @Override + public void onSatelliteModemStateChanged(int state) { + logd("onSatelliteModemStateChanged: state=" + state); + } + }; + int errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged( + SUB_ID, callback); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, errorCode); + verify(mMockSatelliteSessionController, never()) + .registerForSatelliteModemStateChanged(callback); + + resetSatelliteControllerUTToSupportedAndProvisionedState(); + + errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged( + SUB_ID, callback); + assertEquals(SATELLITE_ERROR_NONE, errorCode); + verify(mMockSatelliteSessionController).registerForSatelliteModemStateChanged(callback); + } + + @Test + public void testUnregisterForSatelliteModemStateChanged() { + ISatelliteStateCallback callback = new ISatelliteStateCallback.Stub() { + @Override + public void onSatelliteModemStateChanged(int state) { + logd("onSatelliteModemStateChanged: state=" + state); + } + }; + mSatelliteControllerUT.unregisterForSatelliteModemStateChanged(SUB_ID, callback); + verify(mMockSatelliteSessionController, never()) + .unregisterForSatelliteModemStateChanged(callback); + + resetSatelliteControllerUTToSupportedAndProvisionedState(); + + mSatelliteControllerUT.unregisterForSatelliteModemStateChanged(SUB_ID, callback); + verify(mMockSatelliteSessionController).unregisterForSatelliteModemStateChanged(callback); + } + + @Test + public void testRegisterForSatelliteProvisionStateChanged() { + Semaphore semaphore = new Semaphore(0); + ISatelliteProvisionStateCallback callback = + new ISatelliteProvisionStateCallback.Stub() { + @Override + public void onSatelliteProvisionStateChanged(boolean provisioned) { + logd("onSatelliteProvisionStateChanged: provisioned=" + provisioned); + try { + semaphore.release(); + } catch (Exception ex) { + loge("onSatelliteProvisionStateChanged: Got exception in releasing " + + "semaphore, ex=" + ex); + } + } + }; + int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged( + SUB_ID, callback); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, errorCode); + + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged( + SUB_ID, callback); + assertEquals(SATELLITE_NOT_SUPPORTED, errorCode); + + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged( + SUB_ID, callback); + assertEquals(SATELLITE_ERROR_NONE, errorCode); + + sendProvisionedStateChangedEvent(true, null); + processAllMessages(); + assertTrue(waitForForEvents( + semaphore, 1, "testRegisterForSatelliteProvisionStateChanged")); + + mSatelliteControllerUT.unregisterForSatelliteProvisionStateChanged(SUB_ID, callback); + sendProvisionedStateChangedEvent(true, null); + processAllMessages(); + assertFalse(waitForForEvents( + semaphore, 1, "testRegisterForSatelliteProvisionStateChanged")); + } + + @Test + public void testRegisterForSatelliteDatagram() { + ISatelliteDatagramCallback callback = + new ISatelliteDatagramCallback.Stub() { + @Override + public void onSatelliteDatagramReceived(long datagramId, + @NonNull SatelliteDatagram datagram, int pendingCount, + @NonNull IVoidConsumer internalAck) { + logd("onSatelliteDatagramReceived"); + } + }; + when(mMockDatagramController.registerForSatelliteDatagram(eq(SUB_ID), eq(callback))) + .thenReturn(SATELLITE_ERROR_NONE); + int errorCode = mSatelliteControllerUT.registerForSatelliteDatagram(SUB_ID, callback); + assertEquals(SATELLITE_ERROR_NONE, errorCode); + verify(mMockDatagramController).registerForSatelliteDatagram(eq(SUB_ID), eq(callback)); + } + + @Test + public void testUnregisterForSatelliteDatagram() { + ISatelliteDatagramCallback callback = + new ISatelliteDatagramCallback.Stub() { + @Override + public void onSatelliteDatagramReceived(long datagramId, + @NonNull SatelliteDatagram datagram, int pendingCount, + @NonNull IVoidConsumer internalAck) { + logd("onSatelliteDatagramReceived"); + } + }; + doNothing().when(mMockDatagramController) + .unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback)); + mSatelliteControllerUT.unregisterForSatelliteDatagram(SUB_ID, callback); + verify(mMockDatagramController).unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback)); + } + + @Test + public void testSendSatelliteDatagram() { + String mText = "This is a test datagram message from user"; + SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes()); + + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID, + SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(), + eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true), + any()); + + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + sendProvisionedStateChangedEvent(false, null); + processAllMessages(); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID, + SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0)); + verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(), + eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true), + any()); + + mIIntegerConsumerResults.clear(); + sendProvisionedStateChangedEvent(true, null); + processAllMessages(); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID, + SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer); + processAllMessages(); + assertFalse(waitForIIntegerConsumerResult(1)); + verify(mMockDatagramController, times(1)).sendSatelliteDatagram(anyInt(), + eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true), + any()); + verify(mMockPointingAppController, times(1)).startPointingUI(eq(true)); + } + + @Test + public void testPollPendingSatelliteDatagrams() { + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any()); + + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + sendProvisionedStateChangedEvent(false, null); + processAllMessages(); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0)); + verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any()); + + mIIntegerConsumerResults.clear(); + sendProvisionedStateChangedEvent(true, null); + processAllMessages(); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer); + processAllMessages(); + assertFalse(waitForIIntegerConsumerResult(1)); + verify(mMockDatagramController, times(1)).pollPendingSatelliteDatagrams(anyInt(), any()); + } + + @Test + public void testProvisionSatelliteService() { + String mText = "This is test provision data."; + byte[] testProvisionData = mText.getBytes(); + CancellationSignal cancellationSignal = new CancellationSignal(); + ICancellationSignal cancelRemote = null; + mIIntegerConsumerResults.clear(); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, + testProvisionData, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + assertNull(cancelRemote); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, + testProvisionData, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0)); + assertNull(cancelRemote); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, + testProvisionData, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + assertNull(cancelRemote); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData, + SATELLITE_ERROR_NONE); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, + testProvisionData, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + assertNotNull(cancelRemote); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData, + SATELLITE_NOT_AUTHORIZED); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, + testProvisionData, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_NOT_AUTHORIZED, (long) mIIntegerConsumerResults.get(0)); + assertNotNull(cancelRemote); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData, + SATELLITE_ERROR_NONE); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_NEXT_SATELLITE_TOKEN, testProvisionData, mIIntegerConsumer); + cancellationSignal.setRemote(cancelRemote); + cancellationSignal.cancel(); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verify(mMockSatelliteModemInterface).deprovisionSatelliteService( + eq(TEST_NEXT_SATELLITE_TOKEN), any(Message.class)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpNoResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN); + setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData, + SATELLITE_ERROR_NONE); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, + testProvisionData, mIIntegerConsumer); + cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID, + TEST_NEXT_SATELLITE_TOKEN, + testProvisionData, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_SERVICE_PROVISION_IN_PROGRESS, + (long) mIIntegerConsumerResults.get(0)); + } + + @Test + public void testDeprovisionSatelliteService() { + mIIntegerConsumerSemaphore.drainPermits(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE); + setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + + resetSatelliteControllerUT(); + mIIntegerConsumerResults.clear(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, + SATELLITE_INVALID_MODEM_STATE); + mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID, + TEST_SATELLITE_TOKEN, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0)); + + } + + private void resetSatelliteControllerUTEnabledState() { + logd("resetSatelliteControllerUTEnabledState"); + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE); + doReturn(true).when(mMockSatelliteModemInterface) + .setSatelliteServicePackageName(anyString()); + mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService"); + processAllMessages(); + + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + sendProvisionedStateChangedEvent(true, null); + processAllMessages(); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + } + + private void resetSatelliteControllerUT() { + logd("resetSatelliteControllerUT"); + // Trigger cleanUpResources + sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null); + processAllMessages(); + + // Reset all cached states + setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE); + doReturn(true).when(mMockSatelliteModemInterface) + .setSatelliteServicePackageName(anyString()); + mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService"); + processAllMessages(); + } + + private void resetSatelliteControllerUTToSupportedAndProvisionedState() { + resetSatelliteControllerUT(); + setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE); + verifySatelliteSupported(true, SATELLITE_ERROR_NONE); + sendProvisionedStateChangedEvent(true, null); + processAllMessages(); + verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + } + + private void resetSatelliteControllerUTToOffAndProvisionedState() { + resetSatelliteControllerUTToSupportedAndProvisionedState(); + sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null); + processAllMessages(); + verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); + } + + private void resetSatelliteControllerUTToOnAndProvisionedState() { + resetSatelliteControllerUTToOffAndProvisionedState(); + setRadioPower(true); + processAllMessages(); + + 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); + } + + private void setUpResponseForRequestIsSatelliteEnabled(boolean isSatelliteEnabled, + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, isSatelliteEnabled, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).requestIsSatelliteEnabled(any(Message.class)); + } + + private void setUpResponseForRequestIsSatelliteSupported( + boolean isSatelliteSupported, @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, isSatelliteSupported, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).requestIsSatelliteSupported(any(Message.class)); + } + + private void setUpResponseForRequestIsSatelliteAllowedForCurrentLocation( + boolean isSatelliteAllowed, @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, isSatelliteAllowed, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface) + .requestIsSatelliteCommunicationAllowedForCurrentLocation(any(Message.class)); + } + + private void setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation( + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == 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(mMockSatelliteModemInterface) + .requestIsSatelliteCommunicationAllowedForCurrentLocation(any(Message.class)); + } + + private void setUpResponseForRequestTimeForNextSatelliteVisibility( + int satelliteVisibilityTime, @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + int[] visibilityTime = new int[] {satelliteVisibilityTime}; + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, visibilityTime, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface) + .requestTimeForNextSatelliteVisibility(any(Message.class)); + } + + private void setUpNullResponseForRequestTimeForNextSatelliteVisibility( + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == 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(mMockSatelliteModemInterface) + .requestTimeForNextSatelliteVisibility(any(Message.class)); + } + + private void setUpResponseForRequestIsSatelliteProvisioned( + boolean isSatelliteProvisioned, @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + int[] provisioned = new int[] {isSatelliteProvisioned ? 1 : 0}; + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, provisioned, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).requestIsSatelliteProvisioned(any(Message.class)); + } + + private void setUpResponseForRequestSatelliteEnabled( + boolean enabled, boolean demoMode, @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[2]; + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface) + .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class)); + } + + private void setUpNoResponseForRequestSatelliteEnabled(boolean enabled, boolean demoMode) { + doNothing().when(mMockSatelliteModemInterface) + .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class)); + } + + private void setUpResponseForProvisionSatelliteService( + String token, byte[] provisionData, @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[2]; + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface) + .provisionSatelliteService(eq(token), any(byte[].class), any(Message.class)); + } + + private void setUpNoResponseForProvisionSatelliteService(String token) { + doNothing().when(mMockSatelliteModemInterface) + .provisionSatelliteService(eq(token), any(), any(Message.class)); + } + + private void setUpResponseForDeprovisionSatelliteService(String token, + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[1]; + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface) + .deprovisionSatelliteService(eq(token), any(Message.class)); + } + + private void setUpResponseForRequestSatelliteCapabilities( + SatelliteCapabilities satelliteCapabilities, + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, satelliteCapabilities, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).requestSatelliteCapabilities(any(Message.class)); + } + + private boolean waitForForEvents( + Semaphore semaphore, int expectedNumberOfEvents, String caller) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!semaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge(caller + ": Timeout to receive the expected event"); + return false; + } + } catch (Exception ex) { + loge(caller + ": Got exception=" + ex); + return false; + } + } + return true; + } + + private void setUpNullResponseForRequestSatelliteCapabilities( + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == 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(mMockSatelliteModemInterface).requestSatelliteCapabilities(any(Message.class)); + } + + private void setUpResponseForStartSatelliteTransmissionUpdates( + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == 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(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class), + any()); + } + + private void setUpResponseForStopSatelliteTransmissionUpdates( + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == 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(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class), + any()); + } + + private boolean waitForRequestIsSatelliteSupportedResult(int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mSatelliteSupportSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive requestIsSatelliteSupported() callback"); + return false; + } + } catch (Exception ex) { + loge("waitForRequestIsSatelliteSupportedResult: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForRequestIsSatelliteAllowedForCurrentLocationResult( + int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mSatelliteAllowedSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive " + + "requestIsSatelliteCommunicationAllowedForCurrentLocation()" + + " callback"); + return false; + } + } catch (Exception ex) { + loge("waitForRequestIsSatelliteSupportedResult: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForRequestTimeForNextSatelliteVisibilityResult( + int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mSatelliteVisibilityTimeSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive " + + "requestTimeForNextSatelliteVisibility() callback"); + return false; + } + } catch (Exception ex) { + loge("waitForRequestTimeForNextSatelliteVisibilityResult: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForRequestIsSatelliteEnabledResult(int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mIsSatelliteEnabledSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive requestIsSatelliteEnabled() callback"); + return false; + } + } catch (Exception ex) { + loge("waitForRequestIsSatelliteEnabledResult: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForRequestIsSatelliteProvisionedResult(int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mIsSatelliteProvisionedSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive requestIsSatelliteProvisioned() callback"); + return false; + } + } catch (Exception ex) { + loge("waitForRequestIsSatelliteProvisionedResult: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForRequestSatelliteCapabilitiesResult(int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mSatelliteCapabilitiesSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive requestSatelliteCapabilities() callback"); + return false; + } + } catch (Exception ex) { + loge("waitForRequestSatelliteCapabilitiesResult: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForRequestIsDemoModeEnabledResult(int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mIsDemoModeEnabledSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive requestIsDemoModeEnabled() callback"); + return false; + } + } catch (Exception ex) { + loge("waitForRequestIsDemoModeEnabled: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForIIntegerConsumerResult(int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mIIntegerConsumerSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive IIntegerConsumer() callback"); + return false; + } + } catch (Exception ex) { + loge("waitForIIntegerConsumerResult: Got exception=" + ex); + return false; + } + } + return true; + } + + private void verifySatelliteSupported(boolean supported, int expectedErrorCode) { + mSatelliteSupportSemaphore.drainPermits(); + mSatelliteControllerUT.requestIsSatelliteSupported(SUB_ID, mSatelliteSupportReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteSupportedResult(1)); + assertEquals(expectedErrorCode, mQueriedSatelliteSupportedResultCode); + assertEquals(supported, mQueriedSatelliteSupported); + } + + private void verifySatelliteEnabled(boolean enabled, int expectedErrorCode) { + mIsSatelliteEnabledSemaphore.drainPermits(); + mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteEnabledResult(1)); + assertEquals(expectedErrorCode, mQueriedIsSatelliteEnabledResultCode); + assertEquals(enabled, mQueriedIsSatelliteEnabled); + } + + private void verifySatelliteProvisioned(boolean provisioned, int expectedErrorCode) { + mIsSatelliteProvisionedSemaphore.drainPermits(); + mSatelliteControllerUT.requestIsSatelliteProvisioned( + SUB_ID, mIsSatelliteProvisionedReceiver); + processAllMessages(); + assertTrue(waitForRequestIsSatelliteProvisionedResult(1)); + assertEquals(expectedErrorCode, mQueriedIsSatelliteProvisionedResultCode); + assertEquals(provisioned, mQueriedIsSatelliteProvisioned); + } + + private void sendProvisionedStateChangedEvent(boolean provisioned, Throwable exception) { + Message msg = mSatelliteControllerUT.obtainMessage( + 26 /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */); + msg.obj = new AsyncResult(null, provisioned, exception); + msg.sendToTarget(); + } + + private void sendSatelliteModemStateChangedEvent(int state, Throwable exception) { + Message msg = mSatelliteControllerUT.obtainMessage( + 28 /* EVENT_SATELLITE_MODEM_STATE_CHANGED */); + msg.obj = new AsyncResult(null, state, exception); + msg.sendToTarget(); + } + + private void setRadioPower(boolean on) { + mSimulatedCommands.setRadioPower(on, false, false, null); + } + + private static void loge(String message) { + Rlog.e(TAG, message); + } + + private static class TestSharedPreferences + implements SharedPreferences, SharedPreferences.Editor { + private HashMap mValues = new HashMap(); + + public int getValueCount() { + return mValues.size(); + } + + @Override + public Editor edit() { + return this; + } + + @Override + public boolean contains(String key) { + return mValues.containsKey(key); + } + + @Override + public Map getAll() { + return new HashMap(mValues); + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + if (mValues.containsKey(key)) { + return ((Boolean) mValues.get(key)).booleanValue(); + } + return defValue; + } + + @Override + public float getFloat(String key, float defValue) { + if (mValues.containsKey(key)) { + return ((Float) mValues.get(key)).floatValue(); + } + return defValue; + } + + @Override + public int getInt(String key, int defValue) { + if (mValues.containsKey(key)) { + return ((Integer) mValues.get(key)).intValue(); + } + return defValue; + } + + @Override + public long getLong(String key, long defValue) { + if (mValues.containsKey(key)) { + return ((Long) mValues.get(key)).longValue(); + } + return defValue; + } + + @Override + public String getString(String key, String defValue) { + if (mValues.containsKey(key)) return (String) mValues.get(key); + else return defValue; + } + + @SuppressWarnings("unchecked") + @Override + public Set getStringSet(String key, Set defValues) { + if (mValues.containsKey(key)) { + return (Set) mValues.get(key); + } + return defValues; + } + + @Override + public void registerOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public Editor putBoolean(String key, boolean value) { + mValues.put(key, Boolean.valueOf(value)); + return this; + } + + @Override + public Editor putFloat(String key, float value) { + mValues.put(key, value); + return this; + } + + @Override + public Editor putInt(String key, int value) { + mValues.put(key, value); + return this; + } + + @Override + public Editor putLong(String key, long value) { + mValues.put(key, value); + return this; + } + + @Override + public Editor putString(String key, String value) { + mValues.put(key, value); + return this; + } + + @Override + public Editor putStringSet(String key, Set values) { + mValues.put(key, values); + return this; + } + + @Override + public Editor remove(String key) { + mValues.remove(key); + return this; + } + + @Override + public Editor clear() { + mValues.clear(); + return this; + } + + @Override + public boolean commit() { + return true; + } + + @Override + public void apply() { + commit(); + } + } + + private static class TestSatelliteController extends SatelliteController { + public boolean setSettingsKeyForSatelliteModeCalled = false; + public boolean allRadiosDisabled = true; + public int satelliteModeSettingValue = SATELLITE_MODE_ENABLED_FALSE; + + TestSatelliteController(Context context, Looper looper) { + super(context, looper); + logd("Constructing TestSatelliteController"); + } + + @Override + protected void initializeSatelliteModeRadios() { + logd("initializeSatelliteModeRadios"); + } + + @Override + protected void setSettingsKeyForSatelliteMode(int val) { + logd("setSettingsKeyForSatelliteMode: val=" + val); + satelliteModeSettingValue = val; + setSettingsKeyForSatelliteModeCalled = true; + } + + @Override + protected boolean areAllRadiosDisabled() { + return allRadiosDisabled; + } + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..418d0aaede6c2793a5ac36e8776604b92ce63133 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java @@ -0,0 +1,602 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.res.Resources; +import android.os.AsyncResult; +import android.os.Bundle; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.telecom.Call; +import android.telecom.Connection; +import android.telephony.BinderCacheManager; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.ims.RegistrationManager; +import android.telephony.satellite.ISatelliteProvisionStateCallback; +import android.telephony.satellite.SatelliteManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Log; + +import com.android.ims.ImsException; +import com.android.ims.ImsManager; +import com.android.internal.telephony.Phone; +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.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; + +/** + * Unit tests for SatelliteSOSMessageRecommender + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class SatelliteSOSMessageRecommenderTest extends TelephonyTest { + private static final String TAG = "SatelliteSOSMessageRecommenderTest"; + private static final long TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 500; + private static final int PHONE_ID = 0; + private static final String CALL_ID = "CALL_ID"; + private static final String WRONG_CALL_ID = "WRONG_CALL_ID"; + private TestSatelliteController mTestSatelliteController; + private TestImsManager mTestImsManager; + + @Mock + private Context mMockContext; + @Mock + private Resources mResources; + @Mock + private ImsManager.MmTelFeatureConnectionFactory mMmTelFeatureConnectionFactory; + private TestConnection mTestConnection; + private TestSOSMessageRecommender mTestSOSMessageRecommender; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + + when(mMockContext.getMainLooper()).thenReturn(Looper.myLooper()); + when(mMockContext.getResources()).thenReturn(mResources); + when(mResources.getString(com.android.internal.R.string.config_satellite_service_package)) + .thenReturn(""); + mTestSatelliteController = new TestSatelliteController(mMockContext, + Looper.myLooper()); + mTestImsManager = new TestImsManager( + mMockContext, PHONE_ID, mMmTelFeatureConnectionFactory, null, null, null); + mTestConnection = new TestConnection(CALL_ID); + when(mPhone.getServiceState()).thenReturn(mServiceState); + mTestSOSMessageRecommender = new TestSOSMessageRecommender(Looper.myLooper(), + mTestSatelliteController, mTestImsManager, + TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE); + when(mPhone.isImsRegistered()).thenReturn(false); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testTimeoutBeforeEmergencyCallEnd() { + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + + // Wait for the timeout to expires + moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + processAllMessages(); + + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + } + + @Test + public void testStopTrackingCallBeforeTimeout_ConnectionActive() { + testStopTrackingCallBeforeTimeout(Connection.STATE_ACTIVE); + } + + @Test + public void testStopTrackingCallBeforeTimeout_ConnectionDisconnected() { + testStopTrackingCallBeforeTimeout(Connection.STATE_DISCONNECTED); + } + + @Test + public void testImsRegistrationStateChangedBeforeTimeout() { + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + + mTestImsManager.sendImsRegistrationStateChangedEvent(true); + processAllMessages(); + + assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertFalse(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0); + + mTestImsManager.sendImsRegistrationStateChangedEvent(false); + processAllMessages(); + assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted()); + + // Wait for the timeout to expires + moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + processAllMessages(); + + assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + } + + @Test + public void testSatelliteProvisionStateChangedBeforeTimeout() { + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + + mTestSatelliteController.sendProvisionStateChangedEvent( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false); + processAllMessages(); + + assertFalse(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 2, 2, 2); + + mTestSatelliteController.sendProvisionStateChangedEvent( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true); + + // Wait for the timeout to expires + moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + processAllMessages(); + + assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertFalse(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 2, 2); + } + + @Test + public void testEmergencyCallRedialBeforeTimeout() { + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + + Phone newPhone = Mockito.mock(Phone.class); + when(newPhone.getServiceState()).thenReturn(mServiceState); + when(newPhone.isImsRegistered()).thenReturn(false); + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, newPhone); + processAllMessages(); + + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + /** + * Since {@link SatelliteSOSMessageRecommender} always uses + * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} when registering for provision state + * changed events with {@link SatelliteController}, registerForProvisionCount does + * not depend on Phone. + *

+ * Since we use a single mocked ImsManager instance, registerForImsCount does not depend on + * Phone. + */ + assertRegisterForStateChangedEventsTriggered(newPhone, 2, 2, 1); + + // Wait for the timeout to expires + moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + processAllMessages(); + + assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + /** + * Since {@link SatelliteSOSMessageRecommender} always uses + * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} when unregistering for provision + * state changed events with {@link SatelliteController}, unregisterForProvisionCount does + * not depend on Phone. + *

+ * Since we use a single mocked ImsManager instance, unregisterForImsCount does not depend + * on Phone. + */ + assertUnregisterForStateChangedEventsTriggered(newPhone, 2, 2, 1); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + } + + @Test + public void testCellularServiceStateChangedBeforeTimeout_InServiceToOutOfService() { + testCellularServiceStateChangedBeforeTimeout( + ServiceState.STATE_IN_SERVICE, ServiceState.STATE_OUT_OF_SERVICE); + } + + @Test + public void testCellularServiceStateChangedBeforeTimeout_InServiceToPowerOff() { + testCellularServiceStateChangedBeforeTimeout( + ServiceState.STATE_IN_SERVICE, ServiceState.STATE_POWER_OFF); + } + + @Test + public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToOutOfService() { + testCellularServiceStateChangedBeforeTimeout( + ServiceState.STATE_EMERGENCY_ONLY, ServiceState.STATE_OUT_OF_SERVICE); + } + + @Test + public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToPowerOff() { + testCellularServiceStateChangedBeforeTimeout( + ServiceState.STATE_EMERGENCY_ONLY, ServiceState.STATE_POWER_OFF); + } + + @Test + public void testOnEmergencyCallConnectionStateChangedWithWrongCallId() { + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + + mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged( + WRONG_CALL_ID, Connection.STATE_ACTIVE); + processAllMessages(); + + assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertFalse(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + } + + @Test + public void testSatelliteNotAllowedInCurrentLocation() { + mTestSatelliteController.setIsSatelliteCommunicationAllowed(false); + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + /** + * We should have registered for the state change events abd started the timer when + * receiving the event onEmergencyCallStarted. After getting the callback for the result of + * the request requestIsSatelliteCommunicationAllowedForCurrentLocation, the resources + * should be cleaned up. + */ + assertFalse(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + } + + @Test + public void testOnEmergencyCallStarted() { + SatelliteController satelliteController = new SatelliteController( + mMockContext, Looper.myLooper()); + TestSOSMessageRecommender testSOSMessageRecommender = new TestSOSMessageRecommender( + Looper.myLooper(), + satelliteController, mTestImsManager, + TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + testSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + assertFalse(testSOSMessageRecommender.isTimerStarted()); + assertEquals(0, testSOSMessageRecommender.getCountOfTimerStarted()); + } + + private void testStopTrackingCallBeforeTimeout( + @Connection.ConnectionState int connectionState) { + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + + mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(CALL_ID, connectionState); + processAllMessages(); + + assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertFalse(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + } + + private void testCellularServiceStateChangedBeforeTimeout( + @ServiceState.RegState int availableServiceState, + @ServiceState.RegState int unavailableServiceState) { + mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone); + processAllMessages(); + + assertTrue(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + + mTestSOSMessageRecommender.sendServiceStateChangedEvent(availableServiceState); + processAllMessages(); + + assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertFalse(mTestSOSMessageRecommender.isTimerStarted()); + assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted()); + assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0); + + mTestSOSMessageRecommender.sendServiceStateChangedEvent(unavailableServiceState); + processAllMessages(); + assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted()); + + // Wait for the timeout to expires + moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS); + processAllMessages(); + + assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE)); + assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1); + assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted()); + } + + private void assertRegisterForStateChangedEventsTriggered( + Phone phone, int registerForProvisionCount, int registerForImsCount, + int registerForCellularCount) { + assertEquals(registerForProvisionCount, + mTestSatelliteController.getRegisterForSatelliteProvisionStateChangedCalls()); + assertEquals(registerForImsCount, mTestImsManager.getAddRegistrationCallbackCalls()); + verify(phone, times(registerForCellularCount)) + .registerForServiceStateChanged(any(), anyInt(), any()); + } + + private void assertUnregisterForStateChangedEventsTriggered( + Phone phone, int unregisterForProvisionCount, int unregisterForImsCount, + int unregisterForCellularCount) { + assertEquals(unregisterForProvisionCount, + mTestSatelliteController.getUnregisterForSatelliteProvisionStateChangedCalls()); + assertEquals(unregisterForImsCount, mTestImsManager.getRemoveRegistrationListenerCalls()); + verify(phone, times(unregisterForCellularCount)).unregisterForServiceStateChanged(any()); + } + + private static class TestSatelliteController extends SatelliteController { + + private static final String TAG = "TestSatelliteController"; + private final Map> + mProvisionStateChangedCallbacks; + private int mRegisterForSatelliteProvisionStateChangedCalls = 0; + private int mUnregisterForSatelliteProvisionStateChangedCalls = 0; + private boolean mIsSatelliteProvisioned = true; + private boolean mIsSatelliteCommunicationAllowed = true; + + /** + * Create a SatelliteController to act as a backend service of + * {@link SatelliteManager} + * + * @param context The Context for the SatelliteController. + */ + protected TestSatelliteController(Context context, Looper looper) { + super(context, looper); + mProvisionStateChangedCallbacks = new HashMap<>(); + } + + @Override + public Boolean isSatelliteProvisioned() { + return mIsSatelliteProvisioned; + } + + @Override + public boolean isSatelliteSupported() { + return true; + } + + @Override + @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged( + int subId, @NonNull ISatelliteProvisionStateCallback callback) { + mRegisterForSatelliteProvisionStateChangedCalls++; + Set perSubscriptionCallbacks = + mProvisionStateChangedCallbacks.getOrDefault(subId, new HashSet<>()); + perSubscriptionCallbacks.add(callback); + mProvisionStateChangedCallbacks.put(subId, perSubscriptionCallbacks); + return SatelliteManager.SATELLITE_ERROR_NONE; + } + + @Override + public void unregisterForSatelliteProvisionStateChanged( + int subId, @NonNull ISatelliteProvisionStateCallback callback) { + mUnregisterForSatelliteProvisionStateChangedCalls++; + Set perSubscriptionCallbacks = + mProvisionStateChangedCallbacks.get(subId); + if (perSubscriptionCallbacks != null) { + perSubscriptionCallbacks.remove(callback); + } + } + + @Override + public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId, + @NonNull ResultReceiver result) { + Bundle bundle = new Bundle(); + bundle.putBoolean(SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED, + mIsSatelliteCommunicationAllowed); + result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle); + } + + public void setIsSatelliteCommunicationAllowed(boolean allowed) { + mIsSatelliteCommunicationAllowed = allowed; + } + + public int getRegisterForSatelliteProvisionStateChangedCalls() { + return mRegisterForSatelliteProvisionStateChangedCalls; + } + + public int getUnregisterForSatelliteProvisionStateChangedCalls() { + return mUnregisterForSatelliteProvisionStateChangedCalls; + } + + public void sendProvisionStateChangedEvent(int subId, boolean provisioned) { + mIsSatelliteProvisioned = provisioned; + Set perSubscriptionCallbacks = + mProvisionStateChangedCallbacks.get(subId); + if (perSubscriptionCallbacks != null) { + for (ISatelliteProvisionStateCallback callback : perSubscriptionCallbacks) { + try { + callback.onSatelliteProvisionStateChanged(provisioned); + } catch (RemoteException ex) { + Log.e(TAG, "sendProvisionStateChangedEvent: ex=" + ex); + } + } + } + } + } + + private static class TestImsManager extends ImsManager { + + private final Set mCallbacks; + private int mAddRegistrationCallbackCalls = 0; + private int mRemoveRegistrationListenerCalls = 0; + + /** + * Used for testing only to inject dependencies. + */ + TestImsManager(Context context, int phoneId, MmTelFeatureConnectionFactory factory, + SubscriptionManagerProxy subManagerProxy, SettingsProxy settingsProxy, + BinderCacheManager binderCacheManager) { + super(context, phoneId, factory, subManagerProxy, settingsProxy, binderCacheManager); + mCallbacks = new HashSet<>(); + } + + @Override + public void addRegistrationCallback(RegistrationManager.RegistrationCallback callback, + Executor executor) + throws ImsException { + mAddRegistrationCallbackCalls++; + + if (callback == null) { + throw new NullPointerException("registration callback can't be null"); + } + if (executor == null) { + throw new NullPointerException("registration executor can't be null"); + } + + callback.setExecutor(executor); + mCallbacks.add(callback); + } + + @Override + public void removeRegistrationListener(RegistrationManager.RegistrationCallback callback) { + mRemoveRegistrationListenerCalls++; + + if (callback == null) { + throw new NullPointerException("registration callback can't be null"); + } + mCallbacks.remove(callback); + } + + public int getAddRegistrationCallbackCalls() { + return mAddRegistrationCallbackCalls; + } + + public int getRemoveRegistrationListenerCalls() { + return mRemoveRegistrationListenerCalls; + } + + public void sendImsRegistrationStateChangedEvent(boolean registered) { + if (registered) { + for (RegistrationManager.RegistrationCallback callback : mCallbacks) { + callback.onRegistered(null); + } + } else { + for (RegistrationManager.RegistrationCallback callback : mCallbacks) { + callback.onUnregistered(null); + } + } + } + } + + private static class TestSOSMessageRecommender extends SatelliteSOSMessageRecommender { + + /** + * Create an instance of SatelliteSOSMessageRecommender. + * + * @param looper The looper used with the handler of this class. + * @param satelliteController The SatelliteController singleton instance. + * @param imsManager The ImsManager instance associated with the phone, which is + * used for making the emergency call. This argument is not + * null only in unit tests. + * @param timeoutMillis The timeout duration of the timer. + */ + TestSOSMessageRecommender(Looper looper, SatelliteController satelliteController, + ImsManager imsManager, long timeoutMillis) { + super(looper, satelliteController, imsManager, timeoutMillis); + } + + public boolean isTimerStarted() { + return hasMessages(EVENT_TIME_OUT); + } + + public int getCountOfTimerStarted() { + return mCountOfTimerStarted; + } + + public void sendServiceStateChangedEvent(@ServiceState.RegState int state) { + ServiceState serviceState = new ServiceState(); + serviceState.setState(state); + sendMessage(obtainMessage(EVENT_CELLULAR_SERVICE_STATE_CHANGED, + new AsyncResult(null, serviceState, null))); + } + } + + private static class TestConnection extends Connection { + private final Set mSentEvents; + TestConnection(String callId) { + setTelecomCallId(callId); + mSentEvents = new HashSet<>(); + } + + @Override + public void sendConnectionEvent(String event, Bundle extras) { + mSentEvents.add(event); + } + + public boolean isEventSent(String event) { + return mSentEvents.contains(event); + } + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3ccf512211b1508a0d994f7e3de3dc0a73e6345e --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java @@ -0,0 +1,489 @@ +/* + * 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.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING; +import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Looper; +import android.os.Message; +import android.telephony.satellite.ISatelliteStateCallback; +import android.telephony.satellite.SatelliteManager; +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.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Unit tests for SatelliteSessionController + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class SatelliteSessionControllerTest extends TelephonyTest { + private static final String TAG = "SatelliteSessionControllerTest"; + private static final long TEST_SATELLITE_STAY_AT_LISTENING_MILLIS = 200; + private static final long EVENT_PROCESSING_TIME_MILLIS = 100; + + private static final String STATE_UNAVAILABLE = "UnavailableState"; + private static final String STATE_POWER_OFF = "PowerOffState"; + private static final String STATE_IDLE = "IdleState"; + private static final String STATE_TRANSFERRING = "TransferringState"; + private static final String STATE_LISTENING = "ListeningState"; + + private TestSatelliteModemInterface mSatelliteModemInterface; + private TestSatelliteSessionController mTestSatelliteSessionController; + private TestSatelliteStateCallback mTestSatelliteStateCallback; + + @Mock + private SatelliteController mSatelliteController; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + + mSatelliteModemInterface = new TestSatelliteModemInterface( + mContext, mSatelliteController, Looper.myLooper()); + mTestSatelliteSessionController = new TestSatelliteSessionController(mContext, + Looper.myLooper(), true, mSatelliteModemInterface, + TEST_SATELLITE_STAY_AT_LISTENING_MILLIS, + TEST_SATELLITE_STAY_AT_LISTENING_MILLIS); + processAllMessages(); + + mTestSatelliteStateCallback = new TestSatelliteStateCallback(); + mTestSatelliteSessionController.registerForSatelliteModemStateChanged( + mTestSatelliteStateCallback); + assertSuccessfulModemStateChangedCallback( + mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testInitialState() { + /** + * Since satellite is not supported, SatelliteSessionController should move to UNAVAILABLE + * state. + */ + TestSatelliteSessionController sessionController1 = new TestSatelliteSessionController( + mContext, Looper.myLooper(), false, + mSatelliteModemInterface, 100, 100); + assertNotNull(sessionController1); + processAllMessages(); + assertEquals(STATE_UNAVAILABLE, sessionController1.getCurrentStateName()); + + /** + * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state. + */ + TestSatelliteSessionController sessionController2 = new TestSatelliteSessionController( + mContext, Looper.myLooper(), true, + mSatelliteModemInterface, 100, 100); + assertNotNull(sessionController2); + processAllMessages(); + assertEquals(STATE_POWER_OFF, sessionController2.getCurrentStateName()); + } + + @Test + public void testUnavailableState() throws Exception { + /** + * Since satellite is not supported, SatelliteSessionController should move to UNAVAILABLE + * state. + */ + TestSatelliteSessionController sessionController = new TestSatelliteSessionController( + mContext, Looper.myLooper(), false, + mSatelliteModemInterface, 100, 100); + assertNotNull(sessionController); + processAllMessages(); + assertEquals(STATE_UNAVAILABLE, sessionController.getCurrentStateName()); + + /** + * SatelliteSessionController should stay at UNAVAILABLE state even after it receives the + * satellite radio powered-on state changed event. + */ + sessionController.onSatelliteEnabledStateChanged(true); + processAllMessages(); + assertEquals(STATE_UNAVAILABLE, sessionController.getCurrentStateName()); + } + + @Test + public void testStateTransition() { + /** + * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state. + */ + assertNotNull(mTestSatelliteSessionController); + assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName()); + + // Power on the modem. + mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true); + processAllMessages(); + + // SatelliteSessionController should move to IDLE state after the modem is powered on. + assertSuccessfulModemStateChangedCallback( + mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE); + assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Power off the modem. + mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false); + processAllMessages(); + + // SatelliteSessionController should move back to POWER_OFF state. + assertSuccessfulModemStateChangedCallback( + mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF); + assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Power on the modem. + mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true); + processAllMessages(); + + // SatelliteSessionController should move to IDLE state after radio is turned on. + assertSuccessfulModemStateChangedCallback( + mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE); + assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start sending datagrams + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE); + processAllMessages(); + + // SatelliteSessionController should move to TRANSFERRING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Sending datagrams failed + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED, + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE); + processAllMessages(); + + // SatelliteSessionController should move to IDLE state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_IDLE); + assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start sending datagrams again + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE); + processAllMessages(); + + // SatelliteSessionController should move to TRANSFERRING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Sending datagrams is successful and done. + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE); + processAllMessages(); + + // SatelliteSessionController should move to LISTENING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_LISTENING); + assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName()); + assertEquals(1, mSatelliteModemInterface.getListeningEnabledCount()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start receiving datagrams + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + processAllMessages(); + + // SatelliteSessionController should move to TRANSFERRING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertEquals(1, mSatelliteModemInterface.getListeningDisabledCount()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Receiving datagrams is successful and done. + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE); + processAllMessages(); + + // SatelliteSessionController should move to LISTENING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_LISTENING); + assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName()); + assertEquals(2, mSatelliteModemInterface.getListeningEnabledCount()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start receiving datagrams again + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + processAllMessages(); + + // SatelliteSessionController should move to TRANSFERRING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertEquals(2, mSatelliteModemInterface.getListeningDisabledCount()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Receiving datagrams failed. + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED); + processAllMessages(); + + // SatelliteSessionController should move to IDLE state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_IDLE); + assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start receiving datagrams again + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + processAllMessages(); + + // SatelliteSessionController should move to TRANSFERRING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Receiving datagrams is successful and done. + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE); + processAllMessages(); + + // SatelliteSessionController should move to LISTENING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_LISTENING); + assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName()); + assertEquals(3, mSatelliteModemInterface.getListeningEnabledCount()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Wait for timeout + moveTimeForward(TEST_SATELLITE_STAY_AT_LISTENING_MILLIS); + processAllMessages(); + + // SatelliteSessionController should move to IDLE state after timeout + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_IDLE); + assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName()); + assertEquals(3, mSatelliteModemInterface.getListeningDisabledCount()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start receiving datagrams again + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + processAllMessages(); + + // SatelliteSessionController should move to TRANSFERRING state. + assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start sending datagrams + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + processAllMessages(); + + // SatelliteSessionController should stay at TRANSFERRING state. + assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Receiving datagrams failed. + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED); + processAllMessages(); + + // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE + // state. + assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Start receiving datagrams again. + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + processAllMessages(); + + // SatelliteSessionController should stay at TRANSFERRING state. + assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Sending datagrams failed. + mTestSatelliteSessionController.onDatagramTransferStateChanged( + SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING); + processAllMessages(); + + // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE + // state. + assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback); + assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName()); + assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + + // Power off the modem. + mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false); + processAllMessages(); + + // SatelliteSessionController should move to POWER_OFF state. + assertSuccessfulModemStateChangedCallback( + mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF); + assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName()); + assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState()); + } + + private static class TestSatelliteModemInterface extends SatelliteModemInterface { + private final AtomicInteger mListeningEnabledCount = new AtomicInteger(0); + private final AtomicInteger mListeningDisabledCount = new AtomicInteger(0); + + TestSatelliteModemInterface(@NonNull Context context, + SatelliteController satelliteController, @NonNull Looper looper) { + super(context, satelliteController, looper); + mExponentialBackoff.stop(); + } + + @Override + protected void bindService() { + logd("TestSatelliteModemInterface: bindService"); + } + + @Override + protected void unbindService() { + logd("TestSatelliteModemInterface: unbindService"); + } + + @Override + public void requestSatelliteListeningEnabled(boolean enable, int timeout, + @Nullable Message message) { + if (enable) mListeningEnabledCount.incrementAndGet(); + else mListeningDisabledCount.incrementAndGet(); + } + + public int getListeningEnabledCount() { + return mListeningEnabledCount.get(); + } + + public int getListeningDisabledCount() { + return mListeningDisabledCount.get(); + } + } + + private static class TestSatelliteSessionController extends SatelliteSessionController { + TestSatelliteSessionController(Context context, Looper looper, boolean isSatelliteSupported, + SatelliteModemInterface satelliteModemInterface, + long satelliteStayAtListeningFromSendingMillis, + long satelliteStayAtListeningFromReceivingMillis) { + super(context, looper, isSatelliteSupported, satelliteModemInterface, + satelliteStayAtListeningFromSendingMillis, + satelliteStayAtListeningFromReceivingMillis); + } + + public String getCurrentStateName() { + return getCurrentState().getName(); + } + + public boolean isSendingTriggeredDuringTransferringState() { + return mIsSendingTriggeredDuringTransferringState.get(); + } + } + + private static class TestSatelliteStateCallback extends ISatelliteStateCallback.Stub { + private final AtomicInteger mModemState = new AtomicInteger( + SatelliteManager.SATELLITE_MODEM_STATE_OFF); + private final Semaphore mSemaphore = new Semaphore(0); + + @Override + public void onSatelliteModemStateChanged(int state) { + logd("onSatelliteModemStateChanged: state=" + state); + mModemState.set(state); + try { + mSemaphore.release(); + } catch (Exception ex) { + logd("onSatelliteModemStateChanged: Got exception, ex=" + ex); + } + } + + public boolean waitUntilResult() { + try { + if (!mSemaphore.tryAcquire(EVENT_PROCESSING_TIME_MILLIS, TimeUnit.MILLISECONDS)) { + logd("Timeout to receive onSatelliteModemStateChanged"); + return false; + } + return true; + } catch (Exception ex) { + logd("onSatelliteModemStateChanged: Got exception=" + ex); + return false; + } + } + + public int getModemState() { + return mModemState.get(); + } + } + + private static void assertSuccessfulModemStateChangedCallback( + TestSatelliteStateCallback callback, + @SatelliteManager.SatelliteModemState int expectedModemState) { + boolean successful = callback.waitUntilResult(); + assertTrue(successful); + assertEquals(expectedModemState, callback.getModemState()); + } + + private static void assertModemStateChangedCallbackNotCalled( + TestSatelliteStateCallback callback) { + boolean successful = callback.waitUntilResult(); + assertFalse(successful); + } +} 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 9e418d5c8ab6df7b306030e8a09acff8671793c1..989835317838898ca62a32121f95132bf77f5f56 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java @@ -1226,6 +1226,8 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED, 1); assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getEnhanced4GModeEnabled()) .isEqualTo(1); + assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isEnhanced4GModeEnabled()) + .isTrue(); } @Test @@ -1247,6 +1249,8 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_VT_IMS_ENABLED, 1); assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getVideoTelephonyEnabled()) .isEqualTo(1); + assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isVideoTelephonyEnabled()) + .isTrue(); } @Test @@ -1363,6 +1367,8 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_VOIMS_OPT_IN_STATUS, 1); assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1) .getVoImsOptInEnabled()).isEqualTo(1); + assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isVoImsOptInEnabled()) + .isTrue(); } @@ -1613,6 +1619,8 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_IMS_RCS_UCE_ENABLED, 1); assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getRcsUceEnabled()) .isEqualTo(1); + assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isRcsUceEnabled()) + .isTrue(); } @Test @@ -1635,6 +1643,8 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED, 1); assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getCrossSimCallingEnabled()) .isEqualTo(1); + assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isCrossSimCallingEnabled()) + .isTrue(); } @Test @@ -1736,6 +1746,8 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { 1, SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED, 1); assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1) .getNrAdvancedCallingEnabled()).isEqualTo(1); + assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1) + .isNrAdvancedCallingEnabled()).isTrue(); } @Test diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java index d61a57fb34b93c551622f5e7aa2acb022e7c4dcb..e03256bb7a69fc75423a2c4582b743b4f8bd543d 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java @@ -218,6 +218,7 @@ public class SubscriptionInfoInternalTest { public void testEquals() { SubscriptionInfoInternal another = new SubscriptionInfoInternal.Builder(mSubInfo).build(); assertThat(another).isEqualTo(mSubInfo); + assertThat(another.hashCode()).isEqualTo(mSubInfo.hashCode()); } @Test 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 22e10675d7042c0a000eb2de7ac48a9397dd6531..06dd17cc475a4ab6348d7a02b6bcddb64852e221 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java @@ -60,7 +60,6 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; 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; @@ -72,7 +71,6 @@ import android.app.PropertyInvalidatedCache; import android.compat.testing.PlatformCompatChangeRule; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -148,6 +146,8 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { private static final UserHandle FAKE_USER_HANDLE = new UserHandle(12); + private static final UserHandle FAKE_MANAGED_PROFILE_USER_HANDLE = new UserHandle(13); + // mocked private SubscriptionManagerServiceCallback mMockedSubscriptionManagerServiceCallback; private EuiccController mEuiccController; @@ -161,7 +161,6 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { public void setUp() throws Exception { logd("SubscriptionManagerServiceTest +Setup!"); super.setUp(getClass().getSimpleName()); - enableSubscriptionManagerService(true); // Dual-SIM configuration mPhones = new Phone[] {mPhone, mPhone2}; @@ -195,6 +194,7 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { ((MockContentResolver) mContext.getContentResolver()).addProvider( Telephony.Carriers.CONTENT_URI.getAuthority(), mSubscriptionProvider); + mSubscriptionManagerServiceUT = new SubscriptionManagerService(mContext, Looper.myLooper()); monitorTestableLooper(new TestableLooper(getBackgroundHandler().getLooper())); @@ -215,6 +215,9 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { setIdentifierAccess(false); setPhoneNumberAccess(PackageManager.PERMISSION_DENIED); + doReturn(true).when(mUserManager) + .isManagedProfile(eq(FAKE_MANAGED_PROFILE_USER_HANDLE.getIdentifier())); + logd("SubscriptionManagerServiceTest -Setup!"); } @@ -302,13 +305,6 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } - private void enableGetSubscriptionUserHandle() { - Resources mResources = mock(Resources.class); - doReturn(true).when(mResources).getBoolean( - eq(com.android.internal.R.bool.config_enable_get_subscription_user_handle)); - doReturn(mResources).when(mContext).getResources(); - } - @Test public void testBroadcastOnInitialization() { ArgumentCaptor captorIntent = ArgumentCaptor.forClass(Intent.class); @@ -1057,7 +1053,6 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { @Test public void testSetGetSubscriptionUserHandle() { insertSubscription(FAKE_SUBSCRIPTION_INFO1); - enableGetSubscriptionUserHandle(); // Should fail without MANAGE_SUBSCRIPTION_USER_ASSOCIATION assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT @@ -1091,7 +1086,6 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { @Test public void testIsSubscriptionAssociatedWithUser() { insertSubscription(FAKE_SUBSCRIPTION_INFO1); - enableGetSubscriptionUserHandle(); // Should fail without MANAGE_SUBSCRIPTION_USER_ASSOCIATION assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT @@ -1114,6 +1108,13 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1, FAKE_USER_HANDLE)).isEqualTo(true); + + // Work profile is not associated with any subscription + associatedSubInfoList = mSubscriptionManagerServiceUT + .getSubscriptionInfoListAssociatedWithUser(FAKE_MANAGED_PROFILE_USER_HANDLE); + assertThat(associatedSubInfoList.size()).isEqualTo(0); + assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1, + FAKE_MANAGED_PROFILE_USER_HANDLE)).isEqualTo(false); } @Test @@ -1810,9 +1811,9 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE); assertThat(mSubscriptionManagerServiceUT.removeSubInfo(FAKE_ICCID1, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(0); + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(true); assertThat(mSubscriptionManagerServiceUT.removeSubInfo(FAKE_ICCID2, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(0); + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(true); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE); assertThat(mSubscriptionManagerServiceUT.getAllSubInfoList( @@ -2157,8 +2158,13 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { insertSubscription(FAKE_SUBSCRIPTION_INFO1); insertSubscription(FAKE_SUBSCRIPTION_INFO2); - mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE); final StringWriter stringWriter = new StringWriter(); + assertThrows(SecurityException.class, () + -> mSubscriptionManagerServiceUT.dump(new FileDescriptor(), + new PrintWriter(stringWriter), null)); + + mContextFixture.addCallingOrSelfPermission(Manifest.permission.DUMP); + mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE); mSubscriptionManagerServiceUT.dump(new FileDescriptor(), new PrintWriter(stringWriter), null); assertThat(stringWriter.toString().length()).isGreaterThan(0); @@ -2248,7 +2254,7 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); assertThat(mSubscriptionManagerServiceUT.removeSubInfo(FAKE_MAC_ADDRESS1, - SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)).isEqualTo(0); + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)).isEqualTo(true); assertThat(mSubscriptionManagerServiceUT.getAllSubInfoList( CALLING_PACKAGE, CALLING_FEATURE)).isEmpty(); assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false)).isEmpty(); @@ -2362,6 +2368,19 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false)).isEmpty(); } + @Test + public void testSimNotReadyBySimDeactivate() { + insertSubscription(FAKE_SUBSCRIPTION_INFO1); + + mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE); + mSubscriptionManagerServiceUT.updateSimState( + 0, TelephonyManager.SIM_STATE_NOT_READY, null, null); + doReturn(true).when(mUiccProfile).isEmptyProfile(); + processAllMessages(); + + assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false)).isEmpty(); + } + @Test public void testInactiveSimRemoval() { insertSubscription(FAKE_SUBSCRIPTION_INFO2); @@ -2438,4 +2457,62 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfo(2).isEmbedded()) .isEqualTo(true); } + + + @Test + public void testNonNullSubInfoBuilderFromEmbeddedProfile() { + EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1) + .setIccid(FAKE_ICCID1) //can't build profile with null iccid. + .setNickname(null) //nullable + .setServiceProviderName(null) //nullable + .setProfileName(null) //nullable + .setCarrierIdentifier(null) //nullable + .setUiccAccessRule(null) //nullable + .build(); + + EuiccProfileInfo profileInfo2 = new EuiccProfileInfo.Builder(FAKE_ICCID2) + .setIccid(FAKE_ICCID2) //impossible to build profile with null iccid. + .setNickname(null) //nullable + .setCarrierIdentifier(new CarrierIdentifier(FAKE_MCC2, FAKE_MNC2, null, null, null, + null, FAKE_CARRIER_ID2, FAKE_CARRIER_ID2)) //not allow null mcc/mnc. + .setUiccAccessRule(null) //nullable + .build(); + + GetEuiccProfileInfoListResult result = new GetEuiccProfileInfoListResult( + EuiccService.RESULT_OK, new EuiccProfileInfo[]{profileInfo1}, false); + doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(1)); + result = new GetEuiccProfileInfoListResult(EuiccService.RESULT_OK, + new EuiccProfileInfo[]{profileInfo2}, false); + doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(2)); + doReturn(TelephonyManager.INVALID_PORT_INDEX).when(mUiccSlot) + .getPortIndexFromIccId(anyString()); + + mSubscriptionManagerServiceUT.updateEmbeddedSubscriptions(List.of(1, 2), null); + processAllMessages(); + + SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT + .getSubscriptionInfoInternal(1); + assertThat(subInfo.getSubscriptionId()).isEqualTo(1); + assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID1); + assertThat(subInfo.getDisplayName()).isEqualTo(""); + assertThat(subInfo.getDisplayNameSource()).isEqualTo( + SubscriptionManager.NAME_SOURCE_UNKNOWN); + assertThat(subInfo.getMcc()).isEqualTo(""); + assertThat(subInfo.getMnc()).isEqualTo(""); + assertThat(subInfo.isEmbedded()).isTrue(); + assertThat(subInfo.isRemovableEmbedded()).isFalse(); + assertThat(subInfo.getNativeAccessRules()).isEqualTo(new byte[]{}); + + subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(2); + assertThat(subInfo.getSubscriptionId()).isEqualTo(2); + assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID2); + assertThat(subInfo.getDisplayName()).isEqualTo(""); + assertThat(subInfo.getDisplayNameSource()).isEqualTo( + SubscriptionManager.NAME_SOURCE_UNKNOWN); + assertThat(subInfo.getMcc()).isEqualTo(FAKE_MCC2); + assertThat(subInfo.getMnc()).isEqualTo(FAKE_MNC2); + assertThat(subInfo.isEmbedded()).isTrue(); + assertThat(subInfo.isRemovableEmbedded()).isFalse(); + assertThat(subInfo.getNativeAccessRules()).isEqualTo(new byte[]{}); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/AdnRecordCacheTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/AdnRecordCacheTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c040b9e9a18f018a0764a712fdf73592438ce371 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/AdnRecordCacheTest.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.uicc; + +import static com.android.internal.telephony.uicc.IccConstants.EF_ADN; +import static com.android.internal.telephony.uicc.IccConstants.EF_MBDN; +import static com.android.internal.telephony.uicc.IccConstants.EF_PBR; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.os.test.TestLooper; + +import com.android.internal.telephony.CommandException; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.gsm.UsimPhoneBookManager; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; + +public class AdnRecordCacheTest extends TelephonyTest { + + private AdnRecordCacheUT mAdnRecordCache; + private TestLooper mTestLooper; + private Handler mTestHandler; + private IccFileHandler mFhMock; + private UsimPhoneBookManager mUsimPhoneBookManager; + + @SuppressWarnings("ClassCanBeStatic") + private class AdnRecordCacheUT extends AdnRecordCache { + AdnRecordCacheUT(IccFileHandler fh, UsimPhoneBookManager usimPhoneBookManager) { + super(fh, usimPhoneBookManager); + } + + protected void setAdnLikeWriters(int key, ArrayList waiters) { + super.setAdnLikeWriters(key, waiters); + } + + protected void setAdnLikeFiles(int key, ArrayList adnRecordList) { + super.setAdnLikeFiles(key, adnRecordList); + } + + protected void setUserWriteResponse(int key, Message message) { + super.setUserWriteResponse(key, message); + } + + protected UsimPhoneBookManager getUsimPhoneBookManager() { + return super.getUsimPhoneBookManager(); + } + } + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + // Mocked classes + mFhMock = mock(IccFileHandler.class); + mUsimPhoneBookManager = mock(UsimPhoneBookManager.class); + mTestLooper = new TestLooper(); + mTestHandler = new Handler(mTestLooper.getLooper()); + mTestHandler.post( + () -> mAdnRecordCache = new AdnRecordCacheUT(mFhMock, mUsimPhoneBookManager)); + mTestLooper.dispatchAll(); + } + + @After + public void tearDown() throws Exception { + if (mTestLooper != null) { + mTestLooper.dispatchAll(); + mTestLooper = null; + } + mTestHandler.removeCallbacksAndMessages(null); + mTestHandler = null; + mAdnRecordCache = null; + super.tearDown(); + } + + @Test + public void resetTest() { + Message message1 = Message.obtain(mTestHandler); + Message message2 = Message.obtain(mTestHandler); + + // test data to create mAdnLikeWaiters + ArrayList waiters = new ArrayList<>(); + waiters.add(message1); + mAdnRecordCache.setAdnLikeWriters(EF_MBDN, waiters); + + // test data to create mAdnLikeFiles + setAdnLikeFiles(EF_MBDN); + + // test data to create mUserWriteResponse + mAdnRecordCache.setUserWriteResponse(EF_MBDN, message2); + + mAdnRecordCache.reset(); + + mTestLooper.dispatchAll(); + AsyncResult ar1 = (AsyncResult) message1.obj; + AsyncResult ar2 = (AsyncResult) message2.obj; + Assert.assertTrue(ar1.exception.toString().contains("AdnCache reset")); + Assert.assertTrue(ar2.exception.toString().contains("AdnCace reset")); + } + + @Test + public void updateAdnByIndexEfException() { + int efId = 0x6FC5; + Message message = Message.obtain(mTestHandler); + mAdnRecordCache.updateAdnByIndex(efId, null, 0, null, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNotNull(ar.exception); + assertTrue((ar.exception.toString().contains("EF is not known ADN-like EF:0x6FC5"))); + } + + @Test + public void updateAdnByIndex_WriteResponseException() { + int efId = EF_MBDN; + Message message = Message.obtain(mTestHandler); + Message message2 = Message.obtain(mTestHandler); + AdnRecord adnRecord = new AdnRecord("AlphaTag", "123456789"); + // test data to create mUserWriteResponse + mAdnRecordCache.setUserWriteResponse(efId, message2); + mAdnRecordCache.updateAdnByIndex(efId, adnRecord, 0, null, message); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNotNull(ar.exception); + assertTrue((ar.exception.toString().contains("Have pending update for EF:0x6FC7"))); + } + + @Test + public void updateAdnByIndex() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, "success2", null); + response.sendToTarget(); + return response; + }) + .when(mFhMock) + .getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + Assert.assertNotNull(message); + AdnRecord adnRecord = new AdnRecord("AlphaTag", "123456789"); + // test data to create mUserWriteResponse + mAdnRecordCache.updateAdnByIndex(EF_MBDN, adnRecord, 0, null, message); + mTestLooper.startAutoDispatch(); + verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + } + + @Test + public void updateAdnBySearch_EfException() { + int efId = 0x6FC5; + Message message = Message.obtain(mTestHandler); + mAdnRecordCache.updateAdnBySearch(efId, null, null, null, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNotNull(ar.exception); + assertTrue((ar.exception.toString().contains("EF is not known ADN-like EF:0x6FC5"))); + } + + @Test + public void updateAdnBySearch_Exception() { + Message message = Message.obtain(mTestHandler); + mAdnRecordCache.updateAdnBySearch(EF_MBDN, null, null, null, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNotNull(ar.exception); + assertTrue((ar.exception.toString().contains("Adn list not exist for EF:0x6FC7"))); + } + + @Test + public void updateAdnBySearch_AdnListError() { + int efId = EF_MBDN; + setAdnLikeFiles(efId); + Message message = Message.obtain(mTestHandler); + AdnRecord oldAdn = new AdnRecord("oldAlphaTag", "123456789"); + mAdnRecordCache.updateAdnBySearch(efId, oldAdn, null, null, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNotNull(ar.exception); + assertTrue((ar.exception.toString().contains( + "Adn record don't exist for ADN Record 'oldAlphaTag'"))); + } + + @Test + public void updateAdnBySearch_PendingUpdate() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, "success2", null); + response.sendToTarget(); + return response; + }) + .when(mFhMock) + .getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + + int efId = EF_MBDN; + setAdnLikeFiles(efId); + Message message = Message.obtain(mTestHandler); + AdnRecord oldAdn = new AdnRecord("AlphaTag", "123456789"); + mAdnRecordCache.updateAdnBySearch(efId, oldAdn, null, null, message); + mTestLooper.dispatchAll(); + + verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + } + + @Test + public void updateAdnBySearch() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, "success", null); + response.sendToTarget(); + return response; + }) + .when(mFhMock) + .getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + + int efId = EF_MBDN; + setAdnLikeFiles(efId); + Message message = Message.obtain(mTestHandler); + AdnRecord oldAdn = new AdnRecord("AlphaTag", "123456789"); + mAdnRecordCache.updateAdnBySearch(efId, oldAdn, null, null, message); + mTestLooper.dispatchAll(); + + verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + } + + + @Test + public void updateAdnBySearch_AdnException() { + doReturn(null).when(mUsimPhoneBookManager).loadEfFilesFromUsim(); + Message message = Message.obtain(mTestHandler); + AdnRecord oldAdn = new AdnRecord("oldAlphaTag", "123456789"); + mAdnRecordCache.updateAdnBySearch(EF_PBR, oldAdn, null, null, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNotNull(ar.exception); + assertTrue((ar.exception.toString().contains("Adn list not exist for EF:0x4F30"))); + } + + @Test + public void requestLoadAllAdnLike_AlreadyLoadedEf() { + int efId = EF_MBDN; + setAdnLikeFiles(efId); + Message message = Message.obtain(mTestHandler); + mAdnRecordCache.requestLoadAllAdnLike(efId, 0, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNull(ar.exception); + Assert.assertNotNull(ar.result); + } + + @Test + public void requestLoadAllAdnLike_AlreadyLoadingEf() { + int efId = EF_MBDN; + // test data to create mAdnLikeWaiters + Message message = Message.obtain(mTestHandler); + ArrayList waiters = new ArrayList<>(); + waiters.add(message); + mAdnRecordCache.setAdnLikeWriters(efId, waiters); + mAdnRecordCache.requestLoadAllAdnLike(efId, 0, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertNull(ar); + } + + @Test + public void requestLoadAllAdnLike_NotKnownEf() { + Message message = Message.obtain(mTestHandler); + mAdnRecordCache.requestLoadAllAdnLike(EF_MBDN, -1, message); + mTestLooper.dispatchAll(); + + AsyncResult ar = (AsyncResult) message.obj; + Assert.assertTrue(ar.exception.toString().contains("EF is not known ADN-like EF:0x")); + } + + @Test + public void requestLoadAllAdnLike() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, null, new CommandException( + CommandException.Error.REQUEST_NOT_SUPPORTED)); + response.sendToTarget(); + return response; + }) + .when(mFhMock) + .loadEFLinearFixedAll(anyInt(), anyString(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mAdnRecordCache.requestLoadAllAdnLike(EF_ADN, 0x6FC8, message); + mTestLooper.dispatchAll(); + + verify(mFhMock, times(1)).loadEFLinearFixedAll(anyInt(), anyString(), any(Message.class)); + } + + private void setAdnLikeFiles(int ef) { + // test data to create mAdnLikeFiles + ArrayList adnRecordList = new ArrayList<>(); + AdnRecord adnRecord = new AdnRecord("AlphaTag", "123456789"); + adnRecordList.add(adnRecord); + mAdnRecordCache.setAdnLikeFiles(ef, adnRecordList); + } +} \ No newline at end of file diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccFileHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccFileHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..63e68e2efb5aef2a89851c5ece64aa285c259e07 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccFileHandlerTest.java @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.uicc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.os.test.TestLooper; +import android.util.Log; + +import com.android.internal.telephony.CommandException; +import com.android.internal.telephony.CommandsInterface; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; + +public class IccFileHandlerTest { + CommandsInterface mCi; + IccFileHandler mIccFileHandler; + private TestLooper mTestLooper; + private Handler mTestHandler; + + @Before + public void setUp() throws Exception { + mCi = mock(CommandsInterface.class); + mTestLooper = new TestLooper(); + mTestHandler = new Handler(mTestLooper.getLooper()); + mTestHandler.post( + () -> mIccFileHandler = new IccFileHandler(mCi) { + @Override + protected String getEFPath(int efid) { + switch (efid) { + case 0x4f30: + case 0x4f3a: + return "3F007F105F3A"; + } + return ""; + } + + @Override + protected void logd(String s) { + Log.d("IccFileHandlerTest", s); + } + + @Override + protected void loge(String s) { + Log.d("IccFileHandlerTest", s); + } + }); + + + mTestLooper.dispatchAll(); + } + + @After + public void tearDown() throws Exception { + if (mTestLooper != null) { + mTestLooper.dispatchAll(); + mTestLooper = null; + } + mTestHandler.removeCallbacksAndMessages(null); + mTestHandler = null; + mIccFileHandler = null; + mCi = null; + } + + @Test + public void loadEFLinearFixed_WithNullPath() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixed(0, "", 0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFLinearFixed() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixed(0, 0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFImgLinearFixed() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFImgLinearFixed(0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void getEFLinearRecordSize_WithNullPath() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.getEFLinearRecordSize(0, "", message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void getEFLinearRecordSize() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.getEFLinearRecordSize(0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void getEFTransparentRecordSize() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.getEFTransparentRecordSize(0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFLinearFixedAll_FileNotFoundAtGetRecord() throws InterruptedException { + int efId = 0x4f30; + final CountDownLatch latch = new CountDownLatch(1); + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + IccIoResult iir = new IccIoResult(0x94, 0x00, + IccUtils.hexStringToBytes(null)); + AsyncResult.forMessage(response, iir, null); + mTestHandler.postDelayed(latch::countDown, 100); + response.sendToTarget(); + return null; + }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), + anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixedAll(efId, null, message); + mTestLooper.startAutoDispatch(); + latch.await(5, java.util.concurrent.TimeUnit.SECONDS); + AsyncResult ar = (AsyncResult) message.obj; + assertNotNull(ar); + assertTrue(ar.exception instanceof IccFileNotFound); + } + + @Test + public void loadEFLinearFixedAll_FileNotFoundAtReadRecord() throws InterruptedException { + int efid = 0x4f30; + final CountDownLatch latch = new CountDownLatch(2); + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + IccIoResult iir = null; + if (response.what == 6) { + iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes( + "000000454F30040000FFFF01020145")); + latch.countDown(); + } else if (response.what == 7) { + iir = new IccIoResult(0x94, 0x00, IccUtils.hexStringToBytes(null)); + mTestHandler.postDelayed(latch::countDown, 100); + } + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), + anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixedAll(efid, null, message); + mTestLooper.startAutoDispatch(); + latch.await(5, java.util.concurrent.TimeUnit.SECONDS); + AsyncResult ar = (AsyncResult) message.obj; + assertNotNull(ar); + assertTrue(ar.exception instanceof IccFileNotFound); + } + + @Test + public void loadEFLinearFixedAll_ExceptionAtGetRecord() throws InterruptedException { + int efid = 0x4f30; + final CountDownLatch latch = new CountDownLatch(1); + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, null, new CommandException( + CommandException.Error.OPERATION_NOT_ALLOWED)); + response.sendToTarget(); + mTestHandler.postDelayed(latch::countDown, 100); + return null; + }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), + anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixedAll(efid, null, message); + mTestLooper.startAutoDispatch(); + latch.await(5, java.util.concurrent.TimeUnit.SECONDS); + AsyncResult ar = (AsyncResult) message.obj; + assertTrue(ar.exception instanceof CommandException); + assertSame(CommandException.Error.OPERATION_NOT_ALLOWED, + ((CommandException) ar.exception).getCommandError()); + assertNull(ar.result); + } + + @Test + public void loadEFLinearFixedAll_ExceptionAtReadRecord() throws InterruptedException { + int efid = 0x4f30; + final CountDownLatch latch = new CountDownLatch(2); + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + String hexString = null; + IccIoResult iir = null; + if (response.what == 6) { + hexString = "000000454F30040000FFFF01020145"; + iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString)); + AsyncResult.forMessage(response, iir, null); + latch.countDown(); + } else if (response.what == 7) { + AsyncResult.forMessage(response, null, new CommandException( + CommandException.Error.OPERATION_NOT_ALLOWED)); + mTestHandler.postDelayed(latch::countDown, 100); + } + response.sendToTarget(); + mTestHandler.postDelayed(latch::countDown, 100); + return null; + }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), + anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixedAll(efid, null, message); + mTestLooper.startAutoDispatch(); + latch.await(5, java.util.concurrent.TimeUnit.SECONDS); + AsyncResult ar = (AsyncResult) message.obj; + assertTrue(ar.exception instanceof CommandException); + assertSame(CommandException.Error.OPERATION_NOT_ALLOWED, + ((CommandException) ar.exception).getCommandError()); + assertNull(ar.result); + } + + @Test + public void loadEFLinearFixedAll_FullLoop() throws InterruptedException { + int efid = 0x4f30; + final CountDownLatch latch = new CountDownLatch(2); + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + String hexString = null; + if (response.what == 6) { + hexString = "000000454F30040000FFFF01020145"; + latch.countDown(); + } else if (response.what == 7) { + try { + IccFileHandler.LoadLinearFixedContext lc = + (IccFileHandler.LoadLinearFixedContext) response.obj; + if (mIccFileHandler.getEfid(lc) == efid) { + hexString = + "A814C0034F3A01C1034F3202C5034F0904C9034F2109A90FC3034F611" + + "5C4034F1108CA034F5017AA0FC2034F4A03C7034F4B06CB" + + "034F4F16FFFFFFFFFFFFFFFFFFFFFFFFFF"; + } + mTestHandler.postDelayed(latch::countDown, 100); + + } catch (Exception e) { + e.printStackTrace(); + Log.e("UsimFH", e.getMessage()); + } + + } + IccIoResult iir = new IccIoResult(0x90, 0x00, + IccUtils.hexStringToBytes(hexString)); + AsyncResult.forMessage(response, iir, null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixedAll(efid, null, message); + mTestLooper.startAutoDispatch(); + latch.await(5, java.util.concurrent.TimeUnit.SECONDS); + AsyncResult ar = (AsyncResult) message.obj; + assertNotNull(ar); + ArrayList results = (ArrayList) ar.result; + assertEquals( + "A814C0034F3A01C1034F3202C5034F0904C9034F2109A90FC3034F6115C4034F1108CA03" + + "4F5017AA0FC2034F4A03C7034F4B06CB034F4F16FFFFFFFFFFFFFFFFFFFFFFFFFF", + IccUtils.bytesToHexString(results.get(0))); + verify(mCi, times(2)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFLinearFixedAll_WithNullPath() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixedAll(0, "", message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFLinearFixedAll() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFLinearFixedAll(0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFTransparent() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFTransparent(0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFTransparent_WithZeroSize() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFTransparent(0, 0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void loadEFImgTransparent() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + isNull(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.loadEFImgTransparent(0, 0, 0, 0, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void updateEFLinearFixed_WithNullPath() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + anyString(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.updateEFLinearFixed(0, "", 0, new byte[10], null, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), anyString(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void updateEFLinearFixed() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + anyString(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.updateEFLinearFixed(0, 0, new byte[10], null, message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), anyString(), isNull(), isNull(), any(Message.class)); + } + + @Test + public void updateEFTransparent() { + doAnswer( + invocation -> { + Message response = invocation.getArgument(9); + AsyncResult.forMessage(response, "Success", null); + response.sendToTarget(); + return null; + }) + .when(mCi) + .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(), + anyString(), isNull(), isNull(), any(Message.class)); + + Message message = Message.obtain(mTestHandler); + mIccFileHandler.updateEFTransparent(0, new byte[10], message); + verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(), + anyInt(), anyInt(), anyInt(), anyString(), isNull(), isNull(), any(Message.class)); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccIoResultTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccIoResultTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b3e0a85cff25d7da3f9da495b38a48623462f7e4 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccIoResultTest.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.uicc; + +import org.junit.Assert; +import org.junit.Test; + +public class IccIoResultTest { + + @Test + public void check0x90_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x90, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + + Assert.assertTrue(resultStr != null && (!resultStr.contains("Error"))); + } + + @Test + public void check0x91_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x91, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + + Assert.assertTrue(resultStr != null && (!resultStr.contains("Error"))); + } + + @Test + public void check0x9E_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x9E, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + + Assert.assertTrue(resultStr != null && (!resultStr.contains("Error"))); + } + + @Test + public void check0x9F_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x9F, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + + Assert.assertTrue(resultStr != null && (!resultStr.contains("Error"))); + } + + @Test + public void check0x94_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x94, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + + Assert.assertTrue(resultStr != null && (resultStr.contains("no EF selected"))); + + iccIoResult = new IccIoResult(0x94, 0x02, new byte[10]); + resultStr = iccIoResult.toString(); + + Assert.assertTrue( + resultStr != null && (resultStr.contains("out f range (invalid address)"))); + + iccIoResult = new IccIoResult(0x94, 0x04, new byte[10]); + resultStr = iccIoResult.toString(); + + Assert.assertTrue( + resultStr != null && (resultStr.contains("file ID not found/pattern not found"))); + + iccIoResult = new IccIoResult(0x94, 0x08, new byte[10]); + resultStr = iccIoResult.toString(); + + Assert.assertTrue( + resultStr != null && (resultStr.contains("file is inconsistent with the command"))); + } + + @Test + public void check0x98_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x98, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("unknown"))); + + iccIoResult = new IccIoResult(0x98, 0x02, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("no CHV initialized"))); + + iccIoResult = new IccIoResult(0x98, 0x04, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("access condition not fulfilled"))); + + iccIoResult = new IccIoResult(0x98, 0x08, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("in contradiction with CHV status"))); + + iccIoResult = new IccIoResult(0x98, 0x10, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "in contradiction with invalidation status"))); + + iccIoResult = new IccIoResult(0x98, 0x40, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "unsuccessful CHV verification, no attempt left"))); + + iccIoResult = new IccIoResult(0x98, 0x50, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "increase cannot be performed, Max value reached"))); + + iccIoResult = new IccIoResult(0x98, 0x62, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "authentication error, application specific"))); + + iccIoResult = new IccIoResult(0x98, 0x64, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "authentication error, security context not supported"))); + + iccIoResult = new IccIoResult(0x98, 0x65, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("key freshness failure"))); + + iccIoResult = new IccIoResult(0x98, 0x66, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "authentication error, no memory space available"))); + + iccIoResult = new IccIoResult(0x98, 0x67, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "authentication error, no memory space available in EF_MUK"))); + } + + @Test + public void check0x61_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x61, 0x20, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("more response bytes available"))); + } + + @Test + public void check0x62_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x62, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("no information given"))); + + iccIoResult = new IccIoResult(0x62, 0x81, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains( + "part of returned data may be corrupted"))); + + iccIoResult = new IccIoResult(0x62, 0x82, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "end of file/record reached before reading Le bytes"))); + + iccIoResult = new IccIoResult(0x62, 0x83, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("selected file invalidated"))); + + iccIoResult = new IccIoResult(0x62, 0x84, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("selected file in termination state"))); + + iccIoResult = new IccIoResult(0x62, 0xF1, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("more data available"))); + + iccIoResult = new IccIoResult(0x62, 0xF2, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "more data available and proactive command pending"))); + + iccIoResult = new IccIoResult(0x62, 0xF3, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("response data available"))); + + iccIoResult = new IccIoResult(0x62, 0xF4, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("unknown"))); + } + + @Test + public void check0x63_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x63, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains( + "command successful but after using an internal update retry routine 0 " + + "times"))); + + iccIoResult = new IccIoResult(0x63, 0xF1, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("more data expected"))); + + iccIoResult = new IccIoResult(0x63, 0xF2, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains( + "more data expected and proactive command pending"))); + + iccIoResult = new IccIoResult(0x63, 0xF3, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("unknown"))); + } + + @Test + public void check0x64_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x64, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("unknown"))); + + iccIoResult = new IccIoResult(0x64, 0x00, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("no information given"))); + } + + @Test + public void check0x65_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x65, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("unknown"))); + + iccIoResult = new IccIoResult(0x65, 0x00, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue((resultStr != null) && (resultStr.contains( + "no information given, state of non-volatile memory changed"))); + + iccIoResult = new IccIoResult(0x65, 0x81, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("memory problem"))); + } + + @Test + public void check0x67_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x67, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "the interpretation of this status word is command dependent"))); + + iccIoResult = new IccIoResult(0x67, 0x00, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "incorrect parameter P3"))); + } + + @Test + public void check0x68_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x68, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("unknown"))); + + iccIoResult = new IccIoResult(0x68, 0x00, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("no information given"))); + + iccIoResult = new IccIoResult(0x68, 0x81, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("logical channel not supported"))); + + iccIoResult = new IccIoResult(0x68, 0x82, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("secure messaging not supported"))); + } + + @Test + public void check0x69_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x69, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("unknown"))); + + iccIoResult = new IccIoResult(0x69, 0x00, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("no information given"))); + + iccIoResult = new IccIoResult(0x69, 0x81, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains( + "command incompatible with file structure"))); + + iccIoResult = new IccIoResult(0x69, 0x82, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("security status not satisfied"))); + + iccIoResult = new IccIoResult(0x69, 0x83, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("authentication/PIN method blocked"))); + + iccIoResult = new IccIoResult(0x69, 0x84, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("referenced data invalidated"))); + + iccIoResult = new IccIoResult(0x69, 0x85, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("conditions of use not satisfied"))); + + iccIoResult = new IccIoResult(0x69, 0x86, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue((resultStr != null) && (resultStr.contains( + "command not allowed (no EF selected)"))); + + iccIoResult = new IccIoResult(0x69, 0x89, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue((resultStr != null) && (resultStr.contains( + "command not allowed - secure channel - security not satisfied"))); + } + + @Test + public void check0x6A_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x6A, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("unknown"))); + + iccIoResult = new IccIoResult(0x6A, 0x80, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "incorrect parameters in the data field"))); + + iccIoResult = new IccIoResult(0x6A, 0x81, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("function not supported"))); + + iccIoResult = new IccIoResult(0x6A, 0x82, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("file not found"))); + + iccIoResult = new IccIoResult(0x6A, 0x83, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("record not found"))); + + iccIoResult = new IccIoResult(0x6A, 0x84, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("not enough memory space"))); + + iccIoResult = new IccIoResult(0x6A, 0x86, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue( + (resultStr != null) && (resultStr.contains("incorrect parameters P1 to P2"))); + + iccIoResult = new IccIoResult(0x6A, 0x87, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue((resultStr != null) && (resultStr.contains( + "lc inconsistent with P1 to P2"))); + + iccIoResult = new IccIoResult(0x6A, 0x88, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue((resultStr != null) && (resultStr.contains( + "referenced data not found"))); + } + + @Test + public void check0x6B_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x6B, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue( + resultStr != null && (resultStr.contains("incorrect parameter P1 or P2"))); + } + + @Test + public void check0x6C_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x6C, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains("wrong length, retry with "))); + } + + @Test + public void check0x6D_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x6D, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "unknown instruction code given in the command"))); + } + + @Test + public void check0x6E_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x6E, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "wrong instruction class given in the command"))); + } + + @Test + public void check0x6F_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x6F, 0xC0, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "the interpretation of this status word is command dependent"))); + + iccIoResult = new IccIoResult(0x6F, 0x00, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "technical problem with no diagnostic given"))); + } + + @Test + public void check0x92_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x92, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "command successful but after using an internal update retry routine"))); + + iccIoResult = new IccIoResult(0x92, 0x40, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "memory problem"))); + + iccIoResult = new IccIoResult(0x92, 0x41, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "unknown"))); + } + + @Test + public void check0x93_ErrorCodeParsing() { + IccIoResult iccIoResult = new IccIoResult(0x93, 0x00, new byte[10]); + String resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "SIM Application Toolkit is busy. Command cannot be executed" + + " at present, further normal commands are allowed"))); + + iccIoResult = new IccIoResult(0x93, 0x41, new byte[10]); + resultStr = iccIoResult.toString(); + Assert.assertTrue(resultStr != null && (resultStr.contains( + "unknown"))); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java old mode 100755 new mode 100644 diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java index 8324a5b2745e29bc6336206062f57f2e566865df..6f4666ccf13987b835a1df150f988b01a8e1d69f 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java @@ -303,4 +303,77 @@ public class IsimUiccRecordsTest extends TelephonyTest { assertTrue(((CommandException) ar.exception).getCommandError() == CommandException.Error.OPERATION_NOT_ALLOWED); } -} + + @Test + public void testGetSimServiceTable() { + // reading sim service table successfully case + byte[] sst = new byte[9]; + for (int i = 0; i < sst.length; i++) { + if (i % 2 == 0) { + sst[i] = 0; + } else { + sst[i] = 1; + } + } + Message message = mIsimUiccRecordsUT.obtainMessage( + IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject()); + AsyncResult ar = AsyncResult.forMessage(message, sst, null); + mIsimUiccRecordsUT.handleMessage(message); + String mockSst = IccUtils.bytesToHexString(sst); + String resultSst = mIsimUiccRecordsUT.getIsimIst(); + assertEquals(mockSst, resultSst); + } + + @Test + public void testGetSimServiceTableException() { + // sim service table exception handling case + Message message = mIsimUiccRecordsUT.obtainMessage( + IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject()); + AsyncResult ar = AsyncResult.forMessage(message, null, new CommandException( + CommandException.Error.OPERATION_NOT_ALLOWED)); + mIsimUiccRecordsUT.handleMessage(message); + String resultSst = mIsimUiccRecordsUT.getIsimIst(); + assertEquals(null, resultSst); + } + + @Test + public void testGetSsimServiceTableLessTableSize() { + // The less IST table size will not give any problem + byte[] sst = new byte[5]; + for (int i = 0; i < sst.length; i++) { + if (i % 2 == 0) { + sst[i] = 0; + } else { + sst[i] = 1; + } + } + Message message = mIsimUiccRecordsUT.obtainMessage( + IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject()); + AsyncResult ar = AsyncResult.forMessage(message, sst, null); + mIsimUiccRecordsUT.handleMessage(message); + String mockSst = IccUtils.bytesToHexString(sst); + String resultSst = mIsimUiccRecordsUT.getIsimIst(); + assertEquals(mockSst, resultSst); + } + + @Test + public void testGetSsimServiceTableLargeTableSize() { + // The Big IST table size will not give any problem [ in feature the table may grows] + byte[] sst = new byte[99]; + for (int i = 0; i < sst.length; i++) { + if (i % 2 == 0) { + sst[i] = 0; + } else { + sst[i] = 1; + } + } + Message message = mIsimUiccRecordsUT.obtainMessage( + IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject()); + AsyncResult ar = AsyncResult.forMessage(message, sst, null); + mIsimUiccRecordsUT.handleMessage(message); + String mockSst = IccUtils.bytesToHexString(sst); + String resultSst = mIsimUiccRecordsUT.getIsimIst(); + assertEquals(mockSst, resultSst); + } + +} \ No newline at end of file diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/PortUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/PortUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..69d9a7d0047b4ef6c07670a10af66d9ace965e81 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/PortUtilsTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 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.uicc; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; + +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; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class PortUtilsTest extends TelephonyTest { + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + doReturn(IccSlotStatus.MultipleEnabledProfilesMode.NONE) + .when(mUiccController).getSupportedMepMode(anyInt()); + processAllMessages(); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testConvertToHalPortIndex() { + assertEquals(0, PortUtils.convertToHalPortIndex(0, 0)); + doReturn(IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1) + .when(mUiccController).getSupportedMepMode(anyInt()); + assertEquals(1, PortUtils.convertToHalPortIndex(0, 0)); + } + + @Test + public void testConvertFromHalPortIndex() { + assertEquals(0, PortUtils.convertFromHalPortIndex(0, 1, + IccCardStatus.CardState.CARDSTATE_PRESENT, + IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1)); + assertEquals(1, PortUtils.convertFromHalPortIndex(0, 1, + IccCardStatus.CardState.CARDSTATE_PRESENT, + IccSlotStatus.MultipleEnabledProfilesMode.MEP_B)); + assertEquals(1, PortUtils.convertFromHalPortIndex(0, 1, + IccCardStatus.CardState.CARDSTATE_ABSENT, + IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1)); + doReturn(IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1) + .when(mUiccController).getSupportedMepMode(anyInt()); + assertEquals(0, PortUtils.convertFromHalPortIndex(0, 1, + IccCardStatus.CardState.CARDSTATE_ABSENT, + IccSlotStatus.MultipleEnabledProfilesMode.NONE)); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/RuimRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/RuimRecordsTest.java old mode 100755 new mode 100644 diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java index 3d6f7700d66ebc27f6d2983516275aa1732a61cd..e109ebb47532feb3786014c3abcdb3873620f082 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java @@ -22,9 +22,11 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; @@ -32,6 +34,7 @@ import android.os.AsyncResult; import android.os.Handler; import android.os.Message; import android.os.test.TestLooper; +import android.util.Log; import androidx.test.runner.AndroidJUnit4; @@ -41,6 +44,7 @@ import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.uicc.IccRecords.OperatorPlmnInfo; import com.android.internal.telephony.uicc.IccRecords.PlmnNetworkName; +import com.android.telephony.Rlog; import org.junit.After; import org.junit.Before; @@ -50,6 +54,9 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class SIMRecordsTest extends TelephonyTest { @@ -59,6 +66,7 @@ public class SIMRecordsTest extends TelephonyTest { private static final List EMPTY_FPLMN_LIST = new ArrayList<>(); private static final int EF_SIZE = 12; private static final int MAX_NUM_FPLMN = 4; + private static final int SET_VOICE_MAIL_TIMEOUT = 1000; // Mocked classes private IccFileHandler mFhMock; @@ -597,7 +605,6 @@ public class SIMRecordsTest extends TelephonyTest { data[5] = (byte) (lacTacEnd >>> 8); data[6] = (byte) lacTacEnd; data[7] = (byte) pnnIndex; - return data; } @@ -668,4 +675,300 @@ public class SIMRecordsTest extends TelephonyTest { assertEquals(null, mSIMRecordsUT.getSmscIdentity()); assertTrue(ar.exception instanceof CommandException); } + + @Test + public void testGetSimServiceTable() { + // reading sim service table successfully case + byte[] sst = new byte[111]; + for (int i = 0; i < sst.length; i++) { + if (i % 2 == 0) { + sst[i] = 0; + } else { + sst[i] = 1; + } + } + Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_SST_DONE); + AsyncResult ar = AsyncResult.forMessage(message, sst, null); + mSIMRecordsUT.handleMessage(message); + String mockSst = IccUtils.bytesToHexString(sst); + String resultSst = mSIMRecordsUT.getSimServiceTable(); + assertEquals(mockSst, resultSst); + } + + @Test + public void testGetSimServiceTableException() { + // sim service table exception handling case + Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_SST_DONE); + AsyncResult ar = AsyncResult.forMessage(message, null, new CommandException( + CommandException.Error.OPERATION_NOT_ALLOWED)); + mSIMRecordsUT.handleMessage(message); + String resultSst = mSIMRecordsUT.getSimServiceTable(); + assertEquals(null, resultSst); + } + + @Test + public void testGetSsimServiceTableLessTableSize() { + // sim service table reading case + byte[] sst = new byte[12]; + for (int i = 0; i < sst.length; i++) { + if (i % 2 == 0) { + sst[i] = 0; + } else { + sst[i] = 1; + } + } + Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_SST_DONE); + AsyncResult ar = AsyncResult.forMessage(message, sst, null); + mSIMRecordsUT.handleMessage(message); + String mockSst = IccUtils.bytesToHexString(sst); + String resultSst = mSIMRecordsUT.getSimServiceTable(); + assertEquals(mockSst, resultSst); + } + + @Test + public void testSetVoiceMailNumber() throws InterruptedException { + String voiceMailNumber = "1234567890"; + String alphaTag = "Voicemail"; + final CountDownLatch latch = new CountDownLatch(2); + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the first invocation"); + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the second invocation"); + Message response = invocation.getArgument(5); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class), + eq(null), any(Message.class)); + + mSIMRecordsUT.setMailboxIndex(1); + Message message = Message.obtain(mTestHandler); + mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message); + latch.await(5, TimeUnit.SECONDS); + mTestLooper.startAutoDispatch(); + verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(), + any(byte[].class), eq(null), any(Message.class)); + waitUntilConditionIsTrueOrTimeout(new Condition() { + @Override + public Object expected() { + return true; + } + + @Override + public Object actual() { + return mSIMRecordsUT.getVoiceMailNumber() != null; + } + }); + assertEquals(voiceMailNumber, mSIMRecordsUT.getVoiceMailNumber()); + assertEquals(alphaTag, mSIMRecordsUT.getVoiceMailAlphaTag()); + } + + @Test + public void testSetVoiceMailNumberBigAlphatag() throws InterruptedException { + String voiceMailNumber = "1234567890"; + String alphaTag = "VoicemailAlphaTag-VoicemailAlphaTag"; + final CountDownLatch latch = new CountDownLatch(2); + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the first invocation"); + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the second invocation"); + Message response = invocation.getArgument(5); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class), + eq(null), any(Message.class)); + + mSIMRecordsUT.setMailboxIndex(1); + Message message = Message.obtain(mTestHandler); + mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message); + latch.await(8, TimeUnit.SECONDS); + mTestLooper.startAutoDispatch(); + verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(), + any(byte[].class), eq(null), any(Message.class)); + //if attempt to save bugAlphatag which sim don't support so we will make it null + waitUntilConditionIsTrueOrTimeout(new Condition() { + @Override + public Object expected() { + return true; + } + + @Override + public Object actual() { + return mSIMRecordsUT.getVoiceMailNumber() != null; + } + }); + assertEquals(null, mSIMRecordsUT.getVoiceMailAlphaTag()); + assertEquals(voiceMailNumber, mSIMRecordsUT.getVoiceMailNumber()); + } + + @Test + public void testSetVoiceMailNumberUtf16Alphatag() throws InterruptedException { + String voiceMailNumber = "1234567890"; + String alphaTag = "หมายเลขข้อความเสียง"; // Messagerie vocale + final CountDownLatch latch = new CountDownLatch(2); + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the first invocation"); + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the second invocation"); + Message response = invocation.getArgument(5); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class), + eq(null), any(Message.class)); + + mSIMRecordsUT.setMailboxIndex(1); + Message message = Message.obtain(mTestHandler); + mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message); + latch.await(5, TimeUnit.SECONDS); + + mTestLooper.startAutoDispatch(); + verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(), + any(byte[].class), eq(null), any(Message.class)); + waitUntilConditionIsTrueOrTimeout(new Condition() { + @Override + public Object expected() { + return true; + } + + @Override + public Object actual() { + return mSIMRecordsUT.getVoiceMailNumber() != null; + } + }); + assertEquals(voiceMailNumber, mSIMRecordsUT.getVoiceMailNumber()); + //if attempt to save bugAlphatag which sim don't support so we will make it null + assertEquals(null, mSIMRecordsUT.getVoiceMailAlphaTag()); + } + + @Test + public void testSetVoiceMailNullNumber() throws InterruptedException { + String voiceMailNumber = null; + String alphaTag = "VoicemailAlphaTag"; // Messagerie vocale + final CountDownLatch latch = new CountDownLatch(2); + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the first invocation"); + Message response = invocation.getArgument(2); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + + doAnswer(invocation -> { + int[] result = new int[3]; + result[0] = 32; + result[1] = 32; + result[2] = 1; + Rlog.d("SIMRecordsTest", "Executing the second invocation"); + Message response = invocation.getArgument(5); + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + latch.countDown(); + return null; + }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class), + eq(null), any(Message.class)); + + mSIMRecordsUT.setMailboxIndex(1); + Message message = Message.obtain(mTestHandler); + mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message); + latch.await(5, TimeUnit.SECONDS); + mTestLooper.startAutoDispatch(); + verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class)); + verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(), + any(byte[].class), eq(null), any(Message.class)); + waitUntilConditionIsTrueOrTimeout(new Condition() { + @Override + public Object expected() { + return true; + } + + @Override + public Object actual() { + return mSIMRecordsUT.getVoiceMailAlphaTag() != null; + } + }); + assertEquals(null, mSIMRecordsUT.getVoiceMailNumber()); + assertEquals(alphaTag, mSIMRecordsUT.getVoiceMailAlphaTag()); + } + + public interface Condition { + Object expected(); + + Object actual(); + } + + protected void sleep(long ms) { + try { + Thread.sleep(ms); + } catch (Exception e) { + Log.d(TAG, "InterruptedException"); + } + } + + protected void waitUntilConditionIsTrueOrTimeout(Condition condition) { + final long start = System.currentTimeMillis(); + while (!Objects.equals(condition.expected(), condition.actual()) + && System.currentTimeMillis() - start + < (long) SIMRecordsTest.SET_VOICE_MAIL_TIMEOUT) { + sleep(50); + } + assertEquals("Service Unbound", condition.expected(), condition.actual()); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java index b30c3a7c174230f2d3b3b8393b5f7611bd11a474..e4fabc5a718db7d8ba3789fbcdfefe8015c13260 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java @@ -61,7 +61,7 @@ public class UiccCardTest extends TelephonyTest { mIccIoResult = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes("FF40")); mSimulatedCommands.setIccIoResultForApduLogicalChannel(mIccIoResult); mUiccCard = new UiccCard(mContext, mSimulatedCommands, mIccCardStatus, 0 /* phoneId */, - new Object(), false); + new Object(), IccSlotStatus.MultipleEnabledProfilesMode.NONE); processAllMessages(); logd("create UiccCard"); } @@ -97,4 +97,10 @@ public class UiccCardTest extends TelephonyTest { assertNull(mUiccCard.getUiccPort(INVALID_PORT_ID)); assertNotNull(mUiccCard.getUiccPort(TelephonyManager.DEFAULT_PORT_INDEX)); } + + @Test + @SmallTest + public void testGetCardId() { + assertNull(mUiccCard.getCardId()); + } } 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 3deb501b8cb4964ca28dea2393467f3a5a2f0d67..143d7c98721d9011d9b588867d96ad4063d77071 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java @@ -85,7 +85,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString)); AsyncResult ar = new AsyncResult(null, iir, null); message.obj = ar; @@ -93,16 +93,16 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(), - anyInt(), anyInt(), anyString(), any(Message.class)); + anyInt(), anyInt(), anyString(), eq(false) /*isEs10Command*/, any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); processAllMessages(); @@ -327,7 +327,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString)); AsyncResult ar = new AsyncResult(null, iir, null); message.obj = ar; @@ -335,16 +335,16 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(), - anyInt(), anyInt(), anyString(), any(Message.class)); + anyInt(), anyInt(), anyString(), eq(false) /*isEs10Command*/, any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); @@ -388,7 +388,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString)); AsyncResult ar = new AsyncResult(null, iir, null); message.obj = ar; @@ -396,16 +396,16 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(), - anyInt(), anyInt(), anyString(), any(Message.class)); + anyInt(), anyInt(), anyString(), eq(false) /*isEs10Command*/, any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); processAllMessages(); @@ -445,7 +445,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString)); AsyncResult ar = new AsyncResult(null, iir, null); message.obj = ar; @@ -453,16 +453,16 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(), - anyInt(), anyInt(), anyString(), any(Message.class)); + anyInt(), anyInt(), anyString(), eq(false /*isEs10Command*/), any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); processAllMessages(); @@ -509,11 +509,11 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); processAllMessages(); @@ -569,14 +569,14 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { @Override public Void answer(InvocationOnMock invocation) throws Throwable { currentFileId.set((String) invocation.getArguments()[6]); - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; AsyncResult ar = new AsyncResult(null, new int[]{2}, null); message.obj = ar; message.sendToTarget(); return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xA4), eq(0x00), - eq(0x04), eq(0x02), anyString(), any(Message.class)); + eq(0x04), eq(0x02), anyString(), eq(false /*isEs10Command*/), any(Message.class)); // Read binary - since params are identical across files, we need to keep track of which // file was selected most recently and give back that content. @@ -597,7 +597,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(binaryContent.get(currentFileId.get()))); @@ -607,16 +607,16 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xB0), eq(0x00), - eq(0x00), eq(0x00), eq(""), any(Message.class)); + eq(0x00), eq(0x00), eq(""), eq(false /*isEs10Command*/), any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); processAllMessages(); @@ -667,14 +667,14 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { @Override public Void answer(InvocationOnMock invocation) throws Throwable { currentFileId.set((String) invocation.getArguments()[6]); - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; AsyncResult ar = new AsyncResult(null, new int[]{2}, null); message.obj = ar; message.sendToTarget(); return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xA4), eq(0x00), - eq(0x04), eq(0x02), anyString(), any(Message.class)); + eq(0x04), eq(0x02), anyString(), eq(false /*isEs10Command*/), any(Message.class)); // Read binary - since params are identical across files, we need to keep track of which // file was selected most recently and give back that content. @@ -690,7 +690,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(binaryContent.get(currentFileId.get()))); @@ -700,16 +700,16 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xB0), eq(0x00), - eq(0x00), eq(0x00), eq(""), any(Message.class)); + eq(0x00), eq(0x00), eq(""), eq(false /*isEs10Command*/), any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); processAllMessages(); @@ -766,7 +766,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString1)); AsyncResult ar = new AsyncResult(null, iir, null); @@ -775,12 +775,12 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(), - eq(P2), anyInt(), anyString(), any(Message.class)); + eq(P2), anyInt(), anyString(), eq(false), any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[7]; + Message message = (Message) invocation.getArguments()[8]; IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString2)); AsyncResult ar = new AsyncResult(null, iir, null); @@ -789,16 +789,16 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { return null; } }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(), - eq(P2_EXTENDED_DATA), anyInt(), anyString(), any(Message.class)); + eq(P2_EXTENDED_DATA), anyInt(), anyString(), eq(false), any(Message.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - Message message = (Message) invocation.getArguments()[1]; + Message message = (Message) invocation.getArguments()[2]; message.sendToTarget(); return null; } - }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class)); + }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class)); mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null); processAllMessages(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java index 0344e5772f885059c357be5af7f119ef1ee1db06..2ab23f3f19f8879eb1e72ea6226c5b5db97d564a 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java @@ -104,6 +104,8 @@ public class UiccControllerTest extends TelephonyTest { doReturn(PHONE_COUNT).when(mTelephonyManager).getPhoneCount(); doReturn(PHONE_COUNT).when(mTelephonyManager).getSimCount(); + doReturn(IccSlotStatus.MultipleEnabledProfilesMode.NONE) + .when(mMockSlot).getSupportedMepMode(); // set number of slots to 1 mContextFixture.putIntResource(com.android.internal.R.integer.config_num_physical_slots, 1); @@ -114,6 +116,8 @@ public class UiccControllerTest extends TelephonyTest { mIccCardStatus.mCdmaSubscriptionAppIndex = mIccCardStatus.mImsSubscriptionAppIndex = mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1; + mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_PRESENT; + mIccCardStatus.mSupportedMepMode = IccSlotStatus.MultipleEnabledProfilesMode.NONE; mSimulatedCommands.setIccCardStatus(mIccCardStatus); // for testing we pretend slotIndex is set. In reality it would be invalid on older versions // (before 1.2) of hal diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java index bddb0441ba40bb64ef51ad5376abce3fbc6644c7..14e95f103e1374b069b84aae90ec81cb7df4407e 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java @@ -140,7 +140,7 @@ public class UiccPortTest extends TelephonyTest { record = mUiccPort.getOpenLogicalChannelRecord(CHANNEL_ID); assertThat(record).isNull(); - verify(mUiccProfile).iccCloseLogicalChannel(eq(CHANNEL_ID), eq(null)); + verify(mUiccProfile).iccCloseLogicalChannel(eq(CHANNEL_ID), eq(false), eq(null)); } private IccLogicalChannelRequest getIccLogicalChannelRequest() { diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java index 4a3d4cf79638a3abb63142ccd305aa3a4f747140..a9034ebec927af527860c8400bcdf00136671ad4 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.atLeast; @@ -227,7 +227,7 @@ public class UiccProfileTest extends TelephonyTest { anyInt(), isA(Message.class)); verify(mSimulatedCommandsVerifier, times(2)).iccTransmitApduLogicalChannel( anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyString(), - isA(Message.class) + anyBoolean(), isA(Message.class) ); } @@ -577,8 +577,6 @@ public class UiccProfileTest extends TelephonyTest { mUiccProfile.getApplicationIndex(0).getIccRecords().mIccId = fakeIccId; - doReturn(false).when(mSubscriptionController) - .checkPhoneIdAndIccIdMatch(anyInt(), anyString()); doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1) .setIccId("98765").build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(anyInt()); @@ -587,8 +585,6 @@ public class UiccProfileTest extends TelephonyTest { .getString("operator_branding_" + fakeIccId, null); assertNotEquals(fakeBrand, brandInSharedPreference); - doReturn(true).when(mSubscriptionController) - .checkPhoneIdAndIccIdMatch(anyInt(), anyString()); doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1) .setIccId(fakeIccId).build()).when(mSubscriptionManagerService) .getSubscriptionInfoInternal(anyInt()); @@ -607,8 +603,6 @@ public class UiccProfileTest extends TelephonyTest { mUiccProfile.getApplicationIndex(0).getIccRecords().mIccId = fakeIccId1; doReturn(fakeIccId2).when(mSubscriptionInfo).getIccId(); - doReturn(mSubscriptionInfo).when(mSubscriptionController) - .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any()); mUiccProfile.setOperatorBrandOverride(fakeBrand); String brandInSharedPreference = mContext.getSharedPreferences("file name", 0) diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java index 6846b94471bdf805f7a63ba8c2899d509e30eff4..230f147e32abf657a5203b69af5081668ecb91aa 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java @@ -158,12 +158,7 @@ public class UiccSlotTest extends TelephonyTest { assertTrue(mUiccSlot.isActive()); assertNull(mUiccSlot.getUiccCard()); assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState()); - if (isSubscriptionManagerServiceEnabled()) { - verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); - } else { - verify(mSubInfoRecordUpdater).updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId); - } + verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); } @Test @@ -384,12 +379,7 @@ public class UiccSlotTest extends TelephonyTest { // Make sure when received CARDSTATE_ABSENT state in the first time, mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT; mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex); - if (isSubscriptionManagerServiceEnabled()) { - verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); - } else { - verify(mSubInfoRecordUpdater).updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId); - } + verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState()); assertNull(mUiccSlot.getUiccCard()); } @@ -422,15 +412,9 @@ public class UiccSlotTest extends TelephonyTest { assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState()); // assert that we tried to update subscriptions - if (isSubscriptionManagerServiceEnabled()) { - verify(mUiccController).updateSimStateForInactivePort( - activeIss.mSimPortInfos[0].mLogicalSlotIndex, - inactiveIss.mSimPortInfos[0].mIccId); - } else { - verify(mSubInfoRecordUpdater).updateInternalIccStateForInactivePort( - activeIss.mSimPortInfos[0].mLogicalSlotIndex, - inactiveIss.mSimPortInfos[0].mIccId); - } + verify(mUiccController).updateSimStateForInactivePort( + activeIss.mSimPortInfos[0].mLogicalSlotIndex, + inactiveIss.mSimPortInfos[0].mIccId); } @Test @@ -449,16 +433,10 @@ public class UiccSlotTest extends TelephonyTest { assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState()); assertNotNull(mUiccSlot.getUiccCard()); - // Simulate when SIM is removed, UiccCard and UiccProfile should be disposed and ABSENT - // state is sent to SubscriptionInfoUpdater. + // Simulate when SIM is removed mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT; mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex); - if (isSubscriptionManagerServiceEnabled()) { - verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); - } else { - verify(mSubInfoRecordUpdater).updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId); - } + verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); verify(mUiccProfile).dispose(); assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState()); assertNull(mUiccSlot.getUiccCard()); @@ -481,13 +459,7 @@ public class UiccSlotTest extends TelephonyTest { // radio state unavailable mUiccSlot.onRadioStateUnavailable(phoneId); - // Verify that UNKNOWN state is sent to SubscriptionInfoUpdater in this case. - if (isSubscriptionManagerServiceEnabled()) { - verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.UNKNOWN, null); - } else { - verify(mSubInfoRecordUpdater).updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, phoneId); - } + verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.UNKNOWN, null); assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState()); assertNull(mUiccSlot.getUiccCard()); @@ -495,13 +467,7 @@ public class UiccSlotTest extends TelephonyTest { mIccCardStatus.mCardState = CardState.CARDSTATE_ABSENT; mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex); - // Verify that ABSENT state is sent to SubscriptionInfoUpdater in this case. - if (isSubscriptionManagerServiceEnabled()) { - verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); - } else { - verify(mSubInfoRecordUpdater).updateInternalIccState( - IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId); - } + verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null); assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState()); assertNull(mUiccSlot.getUiccCard()); } @@ -538,4 +504,79 @@ public class UiccSlotTest extends TelephonyTest { assertTrue("EuiccCard should be removable", mUiccSlot.isRemovable()); } + @Test + @SmallTest + public void testMultipleEnabledProfilesData() { + IccSlotStatus iss = new IccSlotStatus(); + IccSimPortInfo simPortInfo1 = new IccSimPortInfo(); + simPortInfo1.mPortActive = false; + simPortInfo1.mLogicalSlotIndex = -1; + simPortInfo1.mIccId = "fake-iccid"; + + IccSimPortInfo simPortInfo2 = new IccSimPortInfo(); + simPortInfo2.mPortActive = true; + simPortInfo2.mLogicalSlotIndex = 0; + simPortInfo2.mIccId = "fake-iccid"; + + iss.mSimPortInfos = new IccSimPortInfo[] {simPortInfo1, simPortInfo2}; + iss.cardState = IccCardStatus.CardState.CARDSTATE_PRESENT; + iss.atr = "3B9F97C00AB1FE453FC6838031E073FE211F65D002341569810F21"; + iss.setMultipleEnabledProfilesMode(3); + + + // initial state + assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState()); + assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.NONE, + mUiccSlot.getSupportedMepMode()); + assertFalse(mUiccSlot.isMultipleEnabledProfileSupported()); + + // update slot to inactive + mUiccSlot.update(null, iss, 0 /* slotIndex */); + + // assert on updated values + assertNull(mUiccSlot.getUiccCard()); + assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState()); + assertTrue(mUiccSlot.isMultipleEnabledProfileSupported()); + assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.MEP_B, + mUiccSlot.getSupportedMepMode()); + + iss.mSimPortInfos = new IccSimPortInfo[] {simPortInfo1}; + iss.setMultipleEnabledProfilesMode(1); // Set MEP mode to MEP-A1 + + // update port info and MEP mode + mUiccSlot.update(null, iss, 0 /* slotIndex */); + + // assert on updated values + assertTrue(mUiccSlot.isMultipleEnabledProfileSupported()); + assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1, + mUiccSlot.getSupportedMepMode()); + + //update port info and MEP mode to test HAL version 2.0 + iss.mSimPortInfos = new IccSimPortInfo[] {simPortInfo1, simPortInfo2}; + iss.setMultipleEnabledProfilesMode(0); // Set MEP mode to NONE(assume modem sends) + + // update port info and MEP mode + mUiccSlot.update(null, iss, 0 /* slotIndex */); + assertTrue(mUiccSlot.isMultipleEnabledProfileSupported()); + assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.MEP_B, + mUiccSlot.getSupportedMepMode()); + } + + @Test + @SmallTest + public void testSimStateUnknown() { + int phoneId = 0; + int slotIndex = 0; + // Initially state is unknown + assertTrue(mUiccSlot.isStateUnknown()); + mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT; + mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex); + assertNull(mUiccSlot.getUiccCard()); + // As CardState is absent, state should not be unknown + assertFalse(mUiccSlot.isStateUnknown()); + // radio state unavailable + mUiccSlot.onRadioStateUnavailable(phoneId); + // When radio is not available, state is unknown + assertTrue(mUiccSlot.isStateUnknown()); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java index 18247d38d05cbfb1b6be5dcbe79efa32d8c12b12..7e51badd4052aeb4c8c62f275cb0abbd194c5a8a 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java @@ -99,7 +99,8 @@ public class UiccStateChangedLauncherTest extends TelephonyTest { // The first broadcast should be sent after initialization. UiccCard card = new UiccCard(mContext, mSimulatedCommands, - makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */, new Object(), false); + makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */, new Object(), + IccSlotStatus.MultipleEnabledProfilesMode.NONE); when(UiccController.getInstance().getUiccCardForPhone(0)).thenReturn(card); uiccLauncher.handleMessage(msg); diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java index 79c4af47b8de7cb42efb909dfdc1033f0f56652a..b6dd7bd2d0f712bc9d29bb7a73386d7ebcabc873 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.uicc.euicc; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -34,6 +35,7 @@ import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.uicc.IccCardApplicationStatus; import com.android.internal.telephony.uicc.IccCardStatus; import com.android.internal.telephony.uicc.IccSlotPortMapping; +import com.android.internal.telephony.uicc.IccSlotStatus; import com.android.internal.telephony.uicc.euicc.apdu.LogicalChannelMocker; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; @@ -94,7 +96,7 @@ public class EuiccCardTest extends TelephonyTest { mEuiccCard = new EuiccCard(mContext, mMockCi, mMockIccCardStatus, - 0 /* phoneId */, new Object(), false) { + 0 /* phoneId */, new Object(), IccSlotStatus.MultipleEnabledProfilesMode.NONE) { @Override protected void loadEidAndNotifyRegistrants() {} @@ -133,7 +135,8 @@ public class EuiccCardTest extends TelephonyTest { public void testPassEidInConstructor() { mMockIccCardStatus.eid = "1A2B3C4D"; mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi, - mMockIccCardStatus, 0 /* phoneId */, new Object(), false); + mMockIccCardStatus, 0 /* phoneId */, new Object(), + IccSlotStatus.MultipleEnabledProfilesMode.NONE); final int eventEidReady = 0; Handler handler = new Handler(Looper.myLooper()) { @@ -154,7 +157,8 @@ public class EuiccCardTest extends TelephonyTest { int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000"); mHandler.post(() -> { mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi, - mMockIccCardStatus, 0 /* phoneId */, new Object(), false); + mMockIccCardStatus, 0 /* phoneId */, new Object(), + IccSlotStatus.MultipleEnabledProfilesMode.NONE); }); processAllMessages(); @@ -188,7 +192,7 @@ public class EuiccCardTest extends TelephonyTest { private void verifyStoreData(int channel, String command) { verify(mMockCi, times(1)) .iccTransmitApduLogicalChannel(eq(channel), eq(0x80 | channel), eq(0xE2), eq(0x91), - eq(0), eq(command.length() / 2), eq(command), any()); + eq(0), eq(command.length() / 2), eq(command), anyBoolean(), any()); } private int mockLogicalChannelResponses(Object... responses) { diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java index 90163cf823ac89dc6d1f5356182ed0405b11dcba..d140ca880ecf87fd7967c1d4f628b28244b43674 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -49,6 +50,8 @@ import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.uicc.IccCardApplicationStatus; import com.android.internal.telephony.uicc.IccCardStatus; import com.android.internal.telephony.uicc.IccSlotPortMapping; +import com.android.internal.telephony.uicc.IccSlotStatus; +import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.asn1.Asn1Node; import com.android.internal.telephony.uicc.asn1.InvalidAsn1DataException; @@ -118,7 +121,8 @@ public class EuiccPortTest extends TelephonyTest { mMockIccCardStatus.mSlotPortMapping = new IccSlotPortMapping(); mEuiccPort = new EuiccPort(mContext, mMockCi, mMockIccCardStatus, - 0 /* phoneId */, new Object(), mEuiccCard, false) { + 0 /* phoneId */, new Object(), mEuiccCard, + IccSlotStatus.MultipleEnabledProfilesMode.NONE) { @Override protected byte[] getDeviceId() { return IccUtils.bcdToBytes("987654321012345"); @@ -171,7 +175,7 @@ public class EuiccPortTest extends TelephonyTest { "BF2D14A012E3105A0A896700000000004523019F7001019000"); ResultCaptor resultCaptor = new ResultCaptor<>(); - mEuiccPort.mIsSupportsMultipleEnabledProfiles = true; // MEP capable + mEuiccPort.mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; // MEP capable mEuiccPort.getAllProfiles(resultCaptor, mHandler); processAllMessages(); @@ -180,7 +184,7 @@ public class EuiccPortTest extends TelephonyTest { assertEquals(1, profiles.length); assertEquals("98760000000000543210", profiles[0].getIccid()); assertEquals(EuiccProfileInfo.PROFILE_STATE_ENABLED, profiles[0].getState()); - verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F20"); + verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F24"); } @Test @@ -203,10 +207,10 @@ public class EuiccPortTest extends TelephonyTest { @Test public void testEnabledOnEsimPort_GetAllProfiles() { int channel = mockLogicalChannelResponses( - "BF2D18A016E3145A0A896700000000004523019F7001009F2001019000"); + "BF2D18A016E3145A0A896700000000004523019F7001009F2401019000"); ResultCaptor resultCaptor = new ResultCaptor<>(); - mEuiccPort.mIsSupportsMultipleEnabledProfiles = true; // MEP capable + mEuiccPort.mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; // MEP capable mEuiccPort.getAllProfiles(resultCaptor, mHandler); processAllMessages(); @@ -218,7 +222,7 @@ public class EuiccPortTest extends TelephonyTest { // which is valid port. So the state should be enabled. // (As per MEP state and enabledOnEsimPort concept) assertEquals(EuiccProfileInfo.PROFILE_STATE_ENABLED, profiles[0].getState()); - verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F20"); + verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F24"); } @Test @@ -228,14 +232,14 @@ public class EuiccPortTest extends TelephonyTest { "BF2D14A012E3105A0A896700000000004523FF9F7001009000"); ResultCaptor resultCaptor = new ResultCaptor<>(); - mEuiccPort.mIsSupportsMultipleEnabledProfiles = true; // MEP capable + mEuiccPort.mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; // MEP capable mEuiccPort.getAllProfiles(resultCaptor, mHandler); processAllMessages(); EuiccProfileInfo[] profiles = resultCaptor.result; assertEquals(1, profiles.length); assertEquals(EuiccProfileInfo.PROFILE_STATE_DISABLED, profiles[0].getState()); - verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F20"); + verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F24"); } @Test @@ -374,6 +378,22 @@ public class EuiccPortTest extends TelephonyTest { verifyStoreData(channel, "BF3111A00C5A0A896700000000004523018101FF"); } + @Test + public void testSwitchToProfile_MepA1() { + int channel = mockLogicalChannelResponses("BF31038001039000"); + + ResultCaptor resultCaptor = new ResultCaptor<>(); + mMockIccCardStatus.mSlotPortMapping.mPortIndex = 1; + mEuiccPort.updateSupportedMepMode(MultipleEnabledProfilesMode.MEP_A1); + mEuiccPort.update(mContext, mMockCi, mMockIccCardStatus, mEuiccCard); + mEuiccPort.switchToProfile("98760000000000543210", true, resultCaptor, mHandler); + processAllMessages(); + + assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode()); + // In case of MEP-A1, verify portIndex is shifted or not. + verifyStoreData(channel, "BF3114A00C5A0A896700000000004523018101FF820102"); + } + @Test public void testGetEid() { int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000"); @@ -884,7 +904,7 @@ public class EuiccPortTest extends TelephonyTest { verify(mMockCi, never()) .iccTransmitApduLogicalChannel( eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), - any()); + anyBoolean(), any()); } @Test @@ -917,7 +937,7 @@ public class EuiccPortTest extends TelephonyTest { verify(mMockCi, never()) .iccTransmitApduLogicalChannel( eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), - any()); + anyBoolean(), any()); } @Test @@ -1175,7 +1195,7 @@ public class EuiccPortTest extends TelephonyTest { private void verifyStoreData(int channel, String command) { verify(mMockCi, times(1)) .iccTransmitApduLogicalChannel(eq(channel), eq(0x80 | channel), eq(0xE2), eq(0x91), - eq(0), eq(command.length() / 2), eq(command), any()); + eq(0), eq(command.length() / 2), eq(command), anyBoolean(), any()); } private int mockLogicalChannelResponses(Object... responses) { diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java index 2b9e7671ac2728e576e1407de2707eff86c0a3aa..b073c6af48d6725be2d6ca12ff44fb12a0b41e52 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -119,7 +120,7 @@ public class ApduSenderTest { assertNull(mResponseCaptor.response); assertNull(mResponseCaptor.exception); verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any()); - verify(mMockCi).iccCloseLogicalChannel(eq(channel), any()); + verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any()); } @Test @@ -149,7 +150,7 @@ public class ApduSenderTest { assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq("a"), any()); + eq(3), eq(0), eq("a"), anyBoolean(), any()); } @Test @@ -169,13 +170,13 @@ public class ApduSenderTest { assertEquals("A4", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq("a"), any()); + eq(3), eq(0), eq("a"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(1), eq("ab"), any()); + eq(3), eq(1), eq("ab"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq(""), any()); + eq(3), eq(0), eq(""), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91), - eq(0), eq(2), eq("abcd"), any()); + eq(0), eq(2), eq("abcd"), anyBoolean(), any()); } @Test @@ -196,11 +197,11 @@ public class ApduSenderTest { assertEquals("A3", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq("a"), any()); + eq(3), eq(0), eq("a"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(1), eq("ab"), any()); + eq(3), eq(1), eq("ab"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq(""), any()); + eq(3), eq(0), eq(""), anyBoolean(), any()); } @Test @@ -216,11 +217,11 @@ public class ApduSenderTest { assertEquals("A1A1A1B2B2B2B2C3C3", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq("a"), any()); + eq(3), eq(0), eq("a"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel), eq(0xC0), eq(0), - eq(0), eq(4), eq(""), any()); + eq(0), eq(4), eq(""), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel), eq(0xC0), eq(0), - eq(0), eq(2), eq(""), any()); + eq(0), eq(2), eq(""), anyBoolean(), any()); } @Test @@ -244,15 +245,15 @@ public class ApduSenderTest { assertEquals("C3", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq("a"), any()); + eq(3), eq(0), eq("a"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq("b"), any()); + eq(3), eq(0), eq("b"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), - eq(0), eq(0xFF), eq(s1), any()); + eq(0), eq(0xFF), eq(s1), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), - eq(1), eq(0xFF), eq(s2), any()); + eq(1), eq(0xFF), eq(s2), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91), - eq(2), eq(16), eq(s3), any()); + eq(2), eq(16), eq(s3), anyBoolean(), any()); } @Test @@ -272,9 +273,9 @@ public class ApduSenderTest { assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), - eq(0), eq(0xFF), eq(s1), any()); + eq(0), eq(0xFF), eq(s1), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91), - eq(1), eq(0xFF), eq(s2), any()); + eq(1), eq(0xFF), eq(s2), anyBoolean(), any()); } @Test @@ -290,7 +291,7 @@ public class ApduSenderTest { assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91), - eq(0), eq(0), eq(""), any()); + eq(0), eq(0), eq(""), anyBoolean(), any()); } @Test @@ -313,13 +314,13 @@ public class ApduSenderTest { assertEquals(0x6985, ((ApduException) mResponseCaptor.exception).getApduStatus()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), - eq(3), eq(0), eq("a"), any()); + eq(3), eq(0), eq("a"), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), - eq(0), eq(0xFF), eq(s1), any()); + eq(0), eq(0xFF), eq(s1), anyBoolean(), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11), - eq(1), eq(0xFF), eq(s2), any()); + eq(1), eq(0xFF), eq(s2), anyBoolean(), any()); verify(mMockCi, never()).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), - eq(0x91), eq(2), eq(16), eq(s3), any()); + eq(0x91), eq(2), eq(16), eq(s3), anyBoolean(), any()); } @Test diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java index e9796a1778c3b3d1bf631390216fe3fcf96f1645..27f743fc0ff8e553b1b412046ebc7c7289bb7fce 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java @@ -16,6 +16,7 @@ package com.android.internal.telephony.uicc.euicc.apdu; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -64,32 +65,39 @@ public final class LogicalChannelMocker { public static void mockSendToLogicalChannel(CommandsInterface mockCi, int channel, Object... responseObjects) { ArgumentCaptor response = ArgumentCaptor.forClass(Message.class); + doAnswer(new Answer() { private int mIndex = 0; @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object responseObject = responseObjects[mIndex++]; - boolean isException = responseObject instanceof Throwable; - int sw1 = 0; - int sw2 = 0; - String hex = responseObject.toString(); - if (!isException) { - int l = hex.length(); - sw1 = Integer.parseInt(hex.substring(l - 4, l - 2), 16); - sw2 = Integer.parseInt(hex.substring(l - 2), 16); - hex = hex.substring(0, l - 4); - } - IccIoResult result = isException ? null : new IccIoResult(sw1, sw2, hex); - Throwable exception = isException ? (Throwable) responseObject : null; - - Message msg = response.getValue(); - AsyncResult.forMessage(msg, result, exception); - msg.sendToTarget(); + mockIccTransmitApduLogicalChannelResponse(response, responseObject); return null; } }).when(mockCi).iccTransmitApduLogicalChannel(eq(channel), anyInt(), anyInt(), anyInt(), - anyInt(), anyInt(), anyString(), response.capture()); + anyInt(), anyInt(), anyString(), anyBoolean(), response.capture()); + } + + private static void mockIccTransmitApduLogicalChannelResponse(ArgumentCaptor response, + Object responseObject) throws Throwable { + + boolean isException = responseObject instanceof Throwable; + int sw1 = 0; + int sw2 = 0; + String hex = responseObject.toString(); + if (!isException) { + int l = hex.length(); + sw1 = Integer.parseInt(hex.substring(l - 4, l - 2), 16); + sw2 = Integer.parseInt(hex.substring(l - 2), 16); + hex = hex.substring(0, l - 4); + } + IccIoResult result = isException ? null : new IccIoResult(sw1, sw2, hex); + Throwable exception = isException ? (Throwable) responseObject : null; + + Message msg = response.getValue(); + AsyncResult.forMessage(msg, result, exception); + msg.sendToTarget(); } public static void mockCloseLogicalChannel(CommandsInterface mockCi, int channel) { @@ -99,7 +107,8 @@ public final class LogicalChannelMocker { AsyncResult.forMessage(msg); msg.sendToTarget(); return null; - }).when(mockCi).iccCloseLogicalChannel(eq(channel), response.capture()); + }).when(mockCi).iccCloseLogicalChannel(eq(channel), + eq(true /*isEs10*/), response.capture()); } private static int[] getSelectResponse(String responseHex) {