Merge "[log] debug getDataNetworkType" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f1d877f..355f6ef 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -281,6 +281,16 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.phone.settings.SatelliteConfigViewer"
+ android:label="@string/satellite_config_viewer"
+ android:exported="true"
+ android:theme="@style/DialerSettingsLight">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name="CdmaCallOptions"
android:label="@string/cdma_options"
android:exported="false"
diff --git a/OWNERS b/OWNERS
index 96033ab..dd12ee3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,4 +2,4 @@
per-file *SimPhonebookProvider* = file:platform/packages/apps/Contacts:/OWNERS
-per-file config.xml=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
+per-file config.xml=hwangoo@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
diff --git a/res/layout/radio_info.xml b/res/layout/radio_info.xml
index c4ab74f..9084891 100644
--- a/res/layout/radio_info.xml
+++ b/res/layout/radio_info.xml
@@ -333,6 +333,16 @@
android:text="@string/demo_esos_satellite_string"
/>
+ <!-- Satellite Config Viewer -->
+ <Button android:id="@+id/satellite_config_viewer"
+ android:textSize="14sp"
+ android:layout_marginTop="8dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAllCaps="false"
+ android:text="@string/satellite_config_viewer"
+ />
+
<!-- VoLTE provisioned -->
<Switch android:id="@+id/volte_provisioned_switch"
android:textSize="14sp"
diff --git a/res/layout/satellite_config_viewer.xml b/res/layout/satellite_config_viewer.xml
new file mode 100644
index 0000000..240e697
--- /dev/null
+++ b/res/layout/satellite_config_viewer.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2025 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="120dp"
+ android:layoutDirection="locale"
+ android:textDirection="locale">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <!-- VERSION -->
+ <LinearLayout style="@style/RadioInfo_entry_layout">
+ <TextView android:text="@string/satellite_config_version_label" style="@style/info_label" />
+ <TextView android:id="@+id/version" style="@style/info_value" />
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+ </LinearLayout>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="2dip"
+ android:background="?android:attr/listDivider" />
+
+ <!-- cids, plmns, svcTypes -->
+ <LinearLayout style="@style/RadioInfo_entry_layout">
+ <TextView android:text="@string/satellite_config_service_type_label" style="@style/info_label" />
+ <TextView android:id="@+id/svc_type" style="@style/info_value" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="3dip"
+ android:background="?android:attr/listDivider"/>
+
+ <!-- allow access -->
+ <LinearLayout style="@style/RadioInfo_entry_layout">
+ <TextView android:text="@string/satellite_config_allow_access_label" style="@style/info_label" />
+ <TextView android:id="@+id/allow_access" style="@style/info_value" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="4dip"
+ android:background="?android:attr/listDivider"/>
+
+ <!-- country codes -->
+ <LinearLayout style="@style/RadioInfo_entry_layout">
+ <TextView android:text="@string/satellite_config_country_code_label" style="@style/info_label" />
+ <TextView android:id="@+id/country_codes" style="@style/info_value" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="5dip"
+ android:background="?android:attr/listDivider"/>
+
+ <!-- size of sats2dat file -->
+ <LinearLayout style="@style/RadioInfo_entry_layout">
+ <TextView android:text="@string/satellite_config_size_of_sats2_dat_label" style="@style/info_label" />
+ <TextView android:id="@+id/size_of_sats2" style="@style/info_value" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+ <!-- satellite access config json -->
+ <LinearLayout style="@style/entry_layout">
+ <TextView android:text="@string/satellite_config_json_label" style="@style/info_label" />
+ <TextView android:id="@+id/config_json" style="@style/info_value" />
+ </LinearLayout>
+
+ </LinearLayout>
+</ScrollView>
+
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 49485ae..7f11fd1 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -85,7 +85,7 @@
<string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"Cuando no se puede contactar con un número, las llamadas se desvían siempre a otro número"</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"Notificaciones"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"Difusiones de emergencia"</string>
- <string name="call_settings" msgid="3677282690157603818">"Ajustes de llamadas"</string>
+ <string name="call_settings" msgid="3677282690157603818">"Ajustes de llamada"</string>
<string name="additional_gsm_call_settings" msgid="1561980168685658846">"Ajustes adicionales"</string>
<string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"Ajustes adicionales (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="sum_gsm_call_settings" msgid="7964692601608878138">"Ajustes adicionales de llamadas solo GSM"</string>
@@ -131,7 +131,7 @@
<string name="disable_cdma_cw" msgid="7119290446496301734">"Cancelar"</string>
<string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Llamada en espera de CDMA en IMS activada"</string>
<string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Llamada en espera de CDMA en IMS desactivada"</string>
- <string name="updating_title" msgid="6130548922615719689">"Ajustes de llamadas"</string>
+ <string name="updating_title" msgid="6130548922615719689">"Ajustes de llamada"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"El administrador es el único usuario que puede cambiar los ajustes de llamada."</string>
<string name="phone_account_settings_user_restriction" msgid="9142685151087208396">"Solo el administrador o el usuario de trabajo pueden cambiar la configuración de la cuenta del teléfono."</string>
<string name="phone_account_no_config_mobile_networks" msgid="7351062247756521227">"El propietario del dispositivo ha restringido la posibilidad de cambiar la configuración de la red móvil."</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 06e81b2..41c98e3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2249,4 +2249,21 @@
<string name="send_from_work_profile_action_str">Switch to work profile</string>
<string name="install_messages_on_work_profile_action_str">Install a work messages app</string>
+ <!-- The title of option menu from phoneInfo test screen, to show satellite config -->
+ <string name="radio_info_data_view_satellite_config">Show Satellite Config</string>
+ <!-- Title of SatelliteConfigViewer screen -->
+ <string name="satellite_config_viewer">Satellite Config Viewer</string>
+ <!-- Satellite config viewer screen. Label for the config data version -->
+ <string name="satellite_config_version_label">version: </string>
+ <!-- Satellite config viewer screen. Label for the subId/plmn/servicetype -->
+ <string name="satellite_config_service_type_label">subId/plmn/servicetype: </string>
+ <!-- Satellite config viewer screen. Label for the allow access -->
+ <string name="satellite_config_allow_access_label">allow_access: </string>
+ <!-- Satellite config viewer screen. Label for the country codes -->
+ <string name="satellite_config_country_code_label">country codes: </string>
+ <!-- Satellite config viewer screen. Label for size of the s2sat.dat file -->
+ <string name="satellite_config_size_of_sats2_dat_label">size of sats2.dat: </string>
+ <!-- Satellite config viewer screen. satellite access config json file -->
+ <string name="satellite_config_json_label">satellite access config json: </string>
+
</resources>
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 1242fec..26ff9c7 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -1928,8 +1928,7 @@
return;
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
Binder.getCallingUserHandle())
|| mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// Skip to check associated telephony feature,
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index e2ae343..65ca6f5 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -1009,8 +1009,7 @@
return;
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
Binder.getCallingUserHandle())
|| mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// Skip to check associated telephony feature,
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 68773f1..df6b766 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -501,11 +501,7 @@
public PhoneGlobals(Context context) {
super(context);
sMe = this;
- if (mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()) {
- if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- mSettingsObserver = new SettingsObserver(context, mHandler);
- }
- } else {
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
mSettingsObserver = new SettingsObserver(context, mHandler);
}
}
@@ -515,9 +511,8 @@
ContentResolver resolver = getContentResolver();
- if (mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- && !getResources().getBoolean(
- com.android.internal.R.bool.config_force_phone_globals_creation)) {
+ if (!getResources().getBoolean(
+ com.android.internal.R.bool.config_force_phone_globals_creation)) {
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
Log.v(LOG_TAG, "onCreate()... but not defined FEATURE_TELEPHONY");
return;
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 5a080aa..8fef578 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -8448,10 +8448,8 @@
public boolean isRttEnabled(int subscriptionId) {
final long identity = Binder.clearCallingIdentity();
try {
- if (mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()) {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
- return false;
- }
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
+ return false;
}
boolean isRttSupported = isRttSupported(subscriptionId);
@@ -11267,8 +11265,7 @@
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
Binder.getCallingUserHandle())) {
if (!isImsAvailableOnDevice()) {
// ProvisioningManager can not handle ServiceSpecificException.
@@ -11872,8 +11869,7 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
Binder.getCallingUserHandle())) {
if (!isImsAvailableOnDevice()) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
@@ -11911,8 +11907,7 @@
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
Binder.getCallingUserHandle())) {
if (!isImsAvailableOnDevice()) {
// operation failed silently
@@ -11945,8 +11940,7 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
Binder.getCallingUserHandle())) {
if (!isImsAvailableOnDevice()) {
// ProvisioningManager can not handle ServiceSpecificException.
@@ -11977,8 +11971,7 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
Binder.getCallingUserHandle())) {
if (!isImsAvailableOnDevice()) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
@@ -13292,6 +13285,7 @@
(r == SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP
|| r == SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED
|| r == SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED))) {
+ Log.d(LOG_TAG, "Satellite access is disallowed for current location.");
result.accept(SATELLITE_RESULT_ACCESS_BARRED);
return;
}
@@ -14190,6 +14184,30 @@
}
/**
+ * This API can be used by only CTS to override the satellite access allowed state for
+ * a list of subscription IDs.
+ *
+ * @param subIdListStr The string representation of the list of subscription IDs,
+ * which are numbers separated by comma.
+ * @return {@code true} if the satellite access allowed state is set successfully,
+ * {@code false} otherwise.
+ */
+ public boolean setSatelliteAccessAllowedForSubscriptions(@Nullable String subIdListStr) {
+ Log.d(LOG_TAG, "setSatelliteAccessAllowedForSubscriptions - " + subIdListStr);
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setSatelliteAccessAllowedForSubscriptions");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setSatelliteAccessAllowedForSubscriptions");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mSatelliteController.setSatelliteAccessAllowedForSubscriptions(subIdListStr);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* 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.
@@ -14260,6 +14278,37 @@
}
/**
+ * This API can be used by only CTS to override TN scanning support.
+ *
+ * @param reset {@code true} mean the overridden configs should not be used, {@code false}
+ * otherwise.
+ * @param concurrentTnScanningSupported Whether concurrent TN scanning is supported.
+ * @param tnScanningDuringSatelliteSessionAllowed Whether TN scanning is allowed during
+ * a satellite session.
+ * @return {@code true} if the TN scanning support is set successfully,
+ * {@code false} otherwise.
+ */
+ public boolean setTnScanningSupport(boolean reset, boolean concurrentTnScanningSupported,
+ boolean tnScanningDuringSatelliteSessionAllowed) {
+ Log.d(LOG_TAG, "setTnScanningSupport: reset= " + reset
+ + ", concurrentTnScanningSupported=" + concurrentTnScanningSupported
+ + ", tnScanningDuringSatelliteSessionAllowed="
+ + tnScanningDuringSatelliteSessionAllowed);
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setTnScanningSupport");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setTnScanningSupport");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mSatelliteController.setTnScanningSupport(reset,
+ concurrentTnScanningSupported, tnScanningDuringSatelliteSessionAllowed);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* This API can be used by only CTS to control ingoring cellular service state event.
*
* @param enabled Whether to enable boolean config.
@@ -14752,8 +14801,7 @@
return;
}
- if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
- || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+ if (!CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
Binder.getCallingUserHandle())
|| mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// Skip to check associated telephony feature,
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index cd6a369..c1692f8 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -189,6 +189,8 @@
"set-satellite-listening-timeout-duration";
private static final String SET_SATELLITE_IGNORE_CELLULAR_SERVICE_STATE =
"set-satellite-ignore-cellular-service-state";
+ private static final String SET_SATELLITE_TN_SCANNING_SUPPORT =
+ "set-satellite-tn-scanning-support";
private static final String SET_SATELLITE_POINTING_UI_CLASS_NAME =
"set-satellite-pointing-ui-class-name";
private static final String SET_DATAGRAM_CONTROLLER_TIMEOUT_DURATION =
@@ -214,6 +216,8 @@
private static final String SET_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT =
"set-satellite-access-restriction-checking-result";
+ private static final String SET_SATELLITE_ACCESS_ALLOWED_FOR_SUBSCRIPTIONS =
+ "set-satellite-access-allowed-for-subscriptions";
private static final String DOMAIN_SELECTION_SUBCOMMAND = "domainselection";
private static final String DOMAIN_SELECTION_SET_SERVICE_OVERRIDE = "set-dss-override";
@@ -432,6 +436,10 @@
return handleSetSatelliteSubscriberIdListChangedIntentComponent();
case SET_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT:
return handleOverrideCarrierRoamingNtnEligibilityChanged();
+ case SET_SATELLITE_ACCESS_ALLOWED_FOR_SUBSCRIPTIONS:
+ return handleSetSatelliteAccessAllowedForSubscriptions();
+ case SET_SATELLITE_TN_SCANNING_SUPPORT:
+ return handleSetSatelliteTnScanningSupport();
case COMMAND_DELETE_IMSI_KEY:
return handleDeleteTestImsiKey();
default: {
@@ -3231,6 +3239,38 @@
return 0;
}
+ private int handleSetSatelliteAccessAllowedForSubscriptions() {
+ PrintWriter errPw = getErrPrintWriter();
+ String subIdListStr = null;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-s": {
+ subIdListStr = getNextArgRequired();
+ break;
+ }
+ }
+ }
+ Log.d(LOG_TAG, "handleSetSatelliteAccessAllowedForSubscriptions: subIdListStr="
+ + subIdListStr);
+
+ try {
+ boolean result = mInterface.setSatelliteAccessAllowedForSubscriptions(subIdListStr);
+ if (VDBG) {
+ Log.v(LOG_TAG, "SetSatelliteAccessAllowedForSubscriptions " + subIdListStr
+ + ", result = " + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "SetSatelliteAccessAllowedForSubscriptions: error = " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+
+ return 0;
+ }
+
private int handleSetSatelliteGatewayServicePackageNameCommand() {
PrintWriter errPw = getErrPrintWriter();
String serviceName = null;
@@ -3411,6 +3451,50 @@
return 0;
}
+ private int handleSetSatelliteTnScanningSupport() {
+ PrintWriter errPw = getErrPrintWriter();
+ boolean reset = false;
+ boolean concurrentTnScanningSupported = false;
+ boolean tnScanningDuringSatelliteSessionAllowed = false;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-r": {
+ reset = true;
+ break;
+ }
+ case "-s": {
+ concurrentTnScanningSupported = Boolean.parseBoolean(getNextArgRequired());
+ break;
+ }
+ case "-a": {
+ tnScanningDuringSatelliteSessionAllowed =
+ Boolean.parseBoolean(getNextArgRequired());
+ break;
+ }
+ }
+ }
+ Log.d(LOG_TAG, "handleSetSatelliteTnScanningSupport: reset=" + reset
+ + ", concurrentTnScanningSupported =" + concurrentTnScanningSupported
+ + ", tnScanningDuringSatelliteSessionAllowed="
+ + tnScanningDuringSatelliteSessionAllowed);
+
+ try {
+ boolean result = mInterface.setTnScanningSupport(reset,
+ concurrentTnScanningSupported, tnScanningDuringSatelliteSessionAllowed);
+ if (VDBG) {
+ Log.v(LOG_TAG, "handleSetSatelliteTnScanningSupport: result = " + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "handleSetSatelliteTnScanningSupport: error = " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
private int handleSetDatagramControllerTimeoutDuration() {
PrintWriter errPw = getErrPrintWriter();
boolean reset = false;
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParser.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParser.java
index ad0926b..b22fb64 100644
--- a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParser.java
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParser.java
@@ -26,8 +26,6 @@
import android.telephony.satellite.SatellitePosition;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
-
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -302,7 +300,6 @@
* @return json string type json contents
*/
@Nullable
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public static String readJsonStringFromFile(@NonNull String jsonFilePath) {
logd("jsonFilePath is " + jsonFilePath);
String json = null;
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
index cfe65c8..aaba0f7 100644
--- a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -60,6 +60,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
@@ -620,7 +621,6 @@
+ satelliteSubscriberProvisionStatus);
}
};
- initializeSatelliteSystemNotification(context);
result = mSatelliteController.registerForSatelliteProvisionStateChanged(
mInternalSatelliteProvisionStateCallback);
plogd("registerForSatelliteProvisionStateChanged result: " + result);
@@ -705,7 +705,9 @@
plogd("EVENT_LOCATION_SETTINGS_ENABLED");
case EVENT_LOCATION_SETTINGS_DISABLED:
// Fall through
+ plogd("EVENT_LOCATION_SETTINGS_DISABLED");
case EVENT_COUNTRY_CODE_CHANGED:
+ plogd("EVENT_COUNTRY_CODE_CHANGED");
handleSatelliteAllowedRegionPossiblyChanged(msg.what);
break;
case CMD_UPDATE_SYSTEM_SELECTION_CHANNELS:
@@ -713,6 +715,7 @@
break;
case EVENT_SATELLITE_SUBSCRIPTION_CHANGED:
plogd("Event: EVENT_SATELLITE_SUBSCRIPTION_CHANGED");
+ initializeSatelliteSystemNotification(mContext);
handleEventDisallowedReasonsChanged();
break;
default:
@@ -825,6 +828,9 @@
if (reset) {
mIsOverlayConfigOverridden = false;
cleanUpCtsResources();
+ cleanUpTelephonyConfigs();
+ cleanUpSatelliteAccessConfigOtaResources();
+ cleanupSatelliteConfigOtaResources();
} else {
mIsOverlayConfigOverridden = true;
mOverriddenIsSatelliteAllowAccessControl = isAllowed;
@@ -836,7 +842,6 @@
+ " does not exist");
mOverriddenSatelliteS2CellFile = null;
}
- ///TODO :: need to check when thi will be reloaded this map
mCachedAccessRestrictionMap.clear();
} else {
mOverriddenSatelliteS2CellFile = null;
@@ -1046,6 +1051,39 @@
}
}
+ private void cleanUpTelephonyConfigs() {
+ mSatelliteController.cleanUpTelephonyConfigs();
+ }
+
+ private void cleanUpSatelliteAccessConfigOtaResources() {
+ PhoneGlobals phoneGlobals = PhoneGlobals.getInstance();
+ File satelliteAccessControlDir =
+ phoneGlobals.getDir(SATELLITE_ACCESS_CONTROL_DATA_DIR, Context.MODE_PRIVATE);
+ if (satelliteAccessControlDir == null || !satelliteAccessControlDir.exists()) {
+ plogd(
+ "cleanUpSatelliteAccessConfigOtaResources: "
+ + SATELLITE_ACCESS_CONTROL_DATA_DIR
+ + " does not exist");
+ return;
+ }
+ plogd(
+ "cleanUpSatelliteAccessConfigOtaResources: Deleting contents under "
+ + SATELLITE_ACCESS_CONTROL_DATA_DIR);
+ FileUtils.deleteContents(satelliteAccessControlDir);
+ }
+
+ private void cleanupSatelliteConfigOtaResources() {
+ SatelliteConfig satelliteConfig = mSatelliteController.getSatelliteConfig();
+ if (satelliteConfig == null) {
+ plogd(
+ "cleanupSatelliteConfigOtaResources: satelliteConfig is null. Cannot or Not"
+ + " needed to delete satellite config OTA files");
+ return;
+ }
+ plogd("cleanupSatelliteConfigOtaResources: Deleting satellite config OTA files");
+ satelliteConfig.cleanOtaResources(mContext);
+ }
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
protected long getElapsedRealtimeNanos() {
return SystemClock.elapsedRealtimeNanos();
@@ -1494,8 +1532,13 @@
}
}
+ /**
+ * Returns a list of satellite country codes.
+ *
+ * @return The list of satellite country codes.
+ */
@NonNull
- private List<String> getSatelliteCountryCodes() {
+ public List<String> getSatelliteCountryCodes() {
synchronized (mLock) {
if (mIsOverlayConfigOverridden) {
return mOverriddenSatelliteCountryCodes;
@@ -1504,8 +1547,13 @@
}
}
+ /**
+ * Returns a satellite s2 cell file
+ *
+ * @return The file of satellite s2 cell
+ */
@Nullable
- protected File getSatelliteS2CellFile() {
+ public File getSatelliteS2CellFile() {
synchronized (mLock) {
if (mIsOverlayConfigOverridden) {
return mOverriddenSatelliteS2CellFile;
@@ -1514,8 +1562,13 @@
}
}
+ /**
+ * Returns a satellite access config file
+ *
+ * @return The file of satellite access config
+ */
@Nullable
- protected File getSatelliteAccessConfigFile() {
+ public File getSatelliteAccessConfigFile() {
synchronized (mLock) {
if (mIsOverlayConfigOverridden) {
logd("mIsOverlayConfigOverridden: " + mIsOverlayConfigOverridden);
@@ -1529,8 +1582,12 @@
}
}
-
- private boolean isSatelliteAllowAccessControl() {
+ /**
+ * Checks if satellite access control is allowed.
+ *
+ * @return {@code true} if satellite access control is allowed, {@code false} otherwise.
+ */
+ public boolean isSatelliteAllowAccessControl() {
synchronized (mLock) {
if (mIsOverlayConfigOverridden) {
return mOverriddenIsSatelliteAllowAccessControl;
@@ -1942,7 +1999,8 @@
.setAutoCancel(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
- .setVisibility(Notification.VISIBILITY_PUBLIC);
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true);
return notificationBuilder.build();
}
@@ -2537,6 +2595,8 @@
*/
private boolean initSatelliteOnDeviceAccessController()
throws IllegalStateException {
+ plogd("initSatelliteOnDeviceAccessController");
+
synchronized (mLock) {
if (getSatelliteS2CellFile() == null) return false;
@@ -2550,6 +2610,10 @@
mSatelliteOnDeviceAccessController =
SatelliteOnDeviceAccessController.create(
getSatelliteS2CellFile(), mFeatureFlags);
+
+ plogd(
+ "initSatelliteOnDeviceAccessController: initialized"
+ + " SatelliteOnDeviceAccessController");
restartKeepOnDeviceAccessControllerResourcesTimer();
mS2Level = mSatelliteOnDeviceAccessController.getS2Level();
plogd("mS2Level=" + mS2Level);
@@ -2705,7 +2769,6 @@
return accessAllowed;
}
-
@Nullable
protected String getSatelliteConfigurationFileNameFromOverlayConfig(
@NonNull Context context) {
@@ -3338,6 +3401,17 @@
return satelliteDisallowedReasons;
}
+ /**
+ * Returns the satellite access configuration version.
+ *
+ * If the satellite config data hasn't been updated by configUpdater,
+ * it returns 0. If it has been updated, it returns the updated version information.
+ */
+ @NonNull
+ public int getSatelliteAccessConfigVersion() {
+ return mSatelliteAccessConfigVersion;
+ }
+
private void plogv(@NonNull String log) {
Rlog.v(TAG, log);
if (mPersistentLogger != null) {
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
index b3c0fdd..60b57c3 100644
--- a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
@@ -37,7 +37,6 @@
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ExponentialBackoff;
@@ -249,6 +248,51 @@
sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT);
}
+ private int[] getServiceTypeForEntitlementMetrics(Map<String, List<Integer>> map) {
+ if (map == null || map.isEmpty()) {
+ return new int[]{};
+ }
+
+ return map.entrySet().stream()
+ .findFirst()
+ .map(entry -> {
+ List<Integer> list = entry.getValue();
+ if (list == null) {
+ return new int[]{}; // Return empty array if the list is null
+ }
+ return list.stream().mapToInt(Integer::intValue).toArray();
+ })
+ .orElse(new int[]{}); // Return empty array if no entry is found
+ }
+
+ private int getDataPolicyForEntitlementMetrics(Map<String, Integer> dataPolicyMap) {
+ if (dataPolicyMap != null && !dataPolicyMap.isEmpty()) {
+ return dataPolicyMap.values().stream().findFirst()
+ .orElse(-1);
+ }
+ return -1;
+ }
+
+ private void reportSuccessForEntitlement(int subId, SatelliteEntitlementResult
+ entitlementResult) {
+ // allowed service info entitlement status
+ boolean isAllowedServiceInfo = !entitlementResult
+ .getAvailableServiceTypeInfoForPlmnList().isEmpty();
+
+ int[] serviceType = new int[0];
+ int dataPolicy = 0;
+ if (isAllowedServiceInfo) {
+ serviceType = getServiceTypeForEntitlementMetrics(
+ entitlementResult.getAvailableServiceTypeInfoForPlmnList());
+ dataPolicy = SatelliteController.getInstance().mapDataPolicyForMetrics(
+ getDataPolicyForEntitlementMetrics(
+ entitlementResult.getDataServicePolicyInfoForPlmnList()));
+ }
+ mEntitlementMetricsStats.reportSuccess(subId,
+ getEntitlementStatus(entitlementResult), true, isAllowedServiceInfo,
+ serviceType, dataPolicy);
+ }
+
/**
* Check if the device can request to entitlement server (if there is an internet connection and
* if the throttle time has passed since the last request), and then pass the response to
@@ -269,8 +313,7 @@
SatelliteEntitlementResult entitlementResult = getSatelliteEntitlementApi(
subId).checkEntitlementStatus();
mSatelliteEntitlementResultPerSub.put(subId, entitlementResult);
- mEntitlementMetricsStats.reportSuccess(subId,
- getEntitlementStatus(entitlementResult), false);
+ reportSuccessForEntitlement(subId, entitlementResult);
}
} catch (ServiceEntitlementException e) {
loge(e.toString());
@@ -337,8 +380,8 @@
SatelliteEntitlementResult entitlementResult = getSatelliteEntitlementApi(
subId).checkEntitlementStatus();
mSatelliteEntitlementResultPerSub.put(subId, entitlementResult);
- mEntitlementMetricsStats.reportSuccess(subId,
- getEntitlementStatus(entitlementResult), true);
+ reportSuccessForEntitlement(subId, entitlementResult);
+
}
} catch (ServiceEntitlementException e) {
loge(e.toString());
diff --git a/src/com/android/phone/security/SafetySourceReceiver.java b/src/com/android/phone/security/SafetySourceReceiver.java
index 99394c2..835c79b 100644
--- a/src/com/android/phone/security/SafetySourceReceiver.java
+++ b/src/com/android/phone/security/SafetySourceReceiver.java
@@ -26,7 +26,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.flags.Flags;
import com.android.phone.PhoneGlobals;
import com.android.telephony.Rlog;
@@ -45,11 +44,7 @@
return;
}
- if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- refreshSafetySources(refreshBroadcastId);
- }
- } else {
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
refreshSafetySources(refreshBroadcastId);
}
}
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 5f0b2c1..c0647d9 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -123,7 +123,6 @@
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.euicc.EuiccConnector;
-import com.android.internal.telephony.satellite.SatelliteServiceUtils;
import com.android.phone.R;
import java.io.IOException;
@@ -134,8 +133,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -335,6 +332,7 @@
private Button mEsosButton;
private Button mSatelliteEnableNonEmergencyModeButton;
private Button mEsosDemoButton;
+ private Button mSatelliteConfigViewerButton;
private Switch mImsVolteProvisionedSwitch;
private Switch mImsVtProvisionedSwitch;
private Switch mImsWfcProvisionedSwitch;
@@ -576,6 +574,7 @@
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ SettingsConstants.setupEdgeToEdge(this);
mSystemUser = android.os.Process.myUserHandle().isSystem();
log("onCreate: mSystemUser=" + mSystemUser);
UserManager userManager = getSystemService(UserManager.class);
@@ -808,6 +807,7 @@
mEsosDemoButton = (Button) findViewById(R.id.demo_esos_questionnaire);
mSatelliteEnableNonEmergencyModeButton = (Button) findViewById(
R.id.satellite_enable_non_emergency_mode);
+ mSatelliteConfigViewerButton = (Button) findViewById(R.id.satellite_config_viewer);
if (shouldHideButton(mActionEsos)) {
mEsosButton.setVisibility(View.GONE);
@@ -837,6 +837,14 @@
});
}
+ mSatelliteConfigViewerButton.setOnClickListener(v -> {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.putExtra("mSubId", mSubId);
+ intent.setClassName("com.android.phone",
+ "com.android.phone.settings.SatelliteConfigViewer");
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ });
+
mOemInfoButton = (Button) findViewById(R.id.oem_info);
mOemInfoButton.setOnClickListener(mOemInfoButtonHandler);
PackageManager pm = getPackageManager();
@@ -2121,31 +2129,38 @@
}
/**
+ * Method will create the PersistableBundle and pack the satellite services like
+ * SMS, MMS, EMERGENCY CALL, DATA in it.
+ *
+ * @return PersistableBundle
+ */
+ public PersistableBundle getSatelliteServicesBundleForOperatorPlmn() {
+ PersistableBundle satServiceBundle = new PersistableBundle();
+ String plmn = mTelephonyManager.getNetworkOperatorForPhone(mPhoneId);
+ if (TextUtils.isEmpty(plmn)) {
+ loge("satData: NetworkOperator PLMN is empty");
+ plmn = mTelephonyManager.getSimOperatorNumeric(mSubId);
+ loge("satData: SimOperator PLMN = " + plmn);
+ }
+ int[] supportedServicesArray = {NetworkRegistrationInfo.SERVICE_TYPE_DATA,
+ NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+ NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY,
+ NetworkRegistrationInfo.SERVICE_TYPE_MMS};
+ satServiceBundle.putIntArray(plmn, supportedServicesArray);
+ log("satData: New SatelliteServicesBundle = " + satServiceBundle);
+ return satServiceBundle;
+ }
+
+ /**
* This method will check the required carrier config keys which plays role in enabling /
* supporting satellite data and update the keys accordingly.
* @param bundleToModify : PersistableBundle
*/
private void updateCarrierConfigToSupportData(PersistableBundle bundleToModify) {
// KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE key info update
- PersistableBundle providerBundle = bundleToModify.getPersistableBundle(
- KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE);
- Map<String, Set<Integer>> satServicesBundle =
- SatelliteServiceUtils.parseSupportedSatelliteServices(providerBundle);
- String plmn = mTelephonyManager.getSimOperatorNumeric(mSubId);
- log("satData: currentplmn = " + plmn);
- if (!satServicesBundle.isEmpty() && satServicesBundle.containsKey(plmn)) {
- Set<Integer> supportedServices = satServicesBundle.get(plmn);
- log("satData: BEFORE supportedServices = " + supportedServices);
- if (!supportedServices.contains(NetworkRegistrationInfo.SERVICE_TYPE_DATA)) {
- supportedServices.add(NetworkRegistrationInfo.SERVICE_TYPE_DATA);
- providerBundle.putIntArray(plmn, supportedServices.stream()
- .mapToInt(Integer::intValue)
- .toArray());
- log("satData: AFTER supportedServices = " + supportedServices);
- }
- bundleToModify.putPersistableBundle(
- KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, providerBundle);
- }
+ bundleToModify.putPersistableBundle(
+ KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+ getSatelliteServicesBundleForOperatorPlmn());
// KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY key info update
int[] availableServices = bundleToModify.getIntArray(
@@ -2165,13 +2180,11 @@
newServices = new int[1];
newServices[0] = NetworkRegistrationInfo.SERVICE_TYPE_DATA;
}
- bundleToModify.putIntArray(
- KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+ bundleToModify.putIntArray(KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
newServices);
// KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL setting to false.
- bundleToModify.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL,
- false);
+ bundleToModify.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
bundleToModify.remove(KEY_SATELLITE_DATA_SUPPORT_MODE_INT);
log("satData: changing carrierConfig to : " + bundleToModify);
getCarrierConfig().overrideConfig(mSubId, bundleToModify, false);
@@ -2312,8 +2325,7 @@
(buttonView, isChecked) -> {
int subId = mSubId;
int phoneId = mPhoneId;
- if (SubscriptionManager.isValidPhoneId(phoneId)
- && isValidSubscription(subId)) {
+ if (SubscriptionManager.isValidPhoneId(phoneId) && isValidSubscription(subId)) {
if (getCarrierConfig() == null) return;
if (isChecked) {
if (!isValidOperator(subId)) {
@@ -2323,26 +2335,15 @@
return;
}
PersistableBundle originalBundle = getCarrierConfig().getConfigForSubId(
- subId,
- KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+ subId, KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL,
- KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE
- );
+ KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE);
PersistableBundle overrideBundle = new PersistableBundle();
- overrideBundle.putBoolean(
- KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+ overrideBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
overrideBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
- PersistableBundle capableProviderBundle = new PersistableBundle();
- capableProviderBundle.putIntArray(mTelephonyManager
- .getNetworkOperatorForPhone(phoneId),
- new int[]{
- // Currently satellite only supports below
- NetworkRegistrationInfo.SERVICE_TYPE_SMS,
- NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY
- });
overrideBundle.putPersistableBundle(
KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
- capableProviderBundle);
+ getSatelliteServicesBundleForOperatorPlmn());
log("mMockSatelliteListener: new " + overrideBundle);
log("mMockSatelliteListener: old " + originalBundle);
getCarrierConfig().overrideConfig(subId, overrideBundle, false);
diff --git a/src/com/android/phone/settings/SatelliteConfigViewer.java b/src/com/android/phone/settings/SatelliteConfigViewer.java
new file mode 100644
index 0000000..4d7309f
--- /dev/null
+++ b/src/com/android/phone/settings/SatelliteConfigViewer.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2025 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.phone.settings;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.SubscriptionManager.getDefaultSubscriptionId;
+
+import android.annotation.ArrayRes;
+import android.annotation.NonNull;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MenuItem;
+import android.widget.TextView;
+
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.phone.R;
+import com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser;
+import com.android.phone.satellite.accesscontrol.SatelliteAccessController;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+
+public class SatelliteConfigViewer extends Activity {
+ private static final String TAG = SatelliteConfigViewer.class.getSimpleName();
+
+ private TextView mVersion;
+ private TextView mServiceType;
+ private TextView mAllowAccess;
+ private TextView mCountryCodes;
+ private TextView mSizeOfSats2;
+ private TextView mConfigAccessJson;
+
+ private SatelliteController mSatelliteController;
+ private SatelliteAccessController mSatelliteAccessController;
+
+ private int mSubId = INVALID_SUBSCRIPTION_ID;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.satellite_config_viewer);
+ Log.d(TAG, "SatelliteConfigViewer: onCreate");
+
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ Intent intentRadioInfo = getIntent();
+ mSubId = intentRadioInfo.getIntExtra("mSubId", getDefaultSubscriptionId());
+ Log.d(TAG, "SatelliteConfigViewer: mSubId: " + mSubId);
+
+ mVersion = (TextView) findViewById(R.id.version);
+ mServiceType = (TextView) findViewById(R.id.svc_type);
+ mAllowAccess = (TextView) findViewById(R.id.allow_access);
+ mCountryCodes = (TextView) findViewById(R.id.country_codes);
+ mSizeOfSats2 = (TextView) findViewById(R.id.size_of_sats2);
+ mConfigAccessJson = (TextView) findViewById(R.id.config_json);
+
+ mSatelliteController = SatelliteController.getInstance();
+ mSatelliteAccessController = SatelliteAccessController.getOrCreateInstance(
+ getApplicationContext(), new FeatureFlagsImpl());
+
+ mVersion.setText(getSatelliteConfigVersion());
+ mServiceType.setText(getSatelliteCarrierConfigUpdateData());
+ mAllowAccess.setText(getSatelliteAllowAccess());
+ mCountryCodes.setText(getSatelliteConfigCountryCodes());
+ mSizeOfSats2.setText(getSatelliteS2SatFileSize(getApplicationContext()));
+ mConfigAccessJson.setText(getSatelliteConfigJsonFile(getApplicationContext()));
+ }
+
+ private String getSatelliteConfigVersion() {
+ logd("getSatelliteConfigVersion");
+ return Integer.toString(mSatelliteAccessController.getSatelliteAccessConfigVersion());
+ }
+
+ private String getSatelliteCarrierConfigUpdateData() {
+ logd("getSatelliteCarrierConfigUpdateData");
+ HashMap<String, List<Integer>> mapPlmnServiceType = new HashMap<>();
+ List<String> plmnList = mSatelliteController.getSatellitePlmnsForCarrier(mSubId);
+ for (String plmn : plmnList) {
+ List<Integer> listServiceType =
+ mSatelliteController.getSupportedSatelliteServicesForPlmn(mSubId, plmn);
+ mapPlmnServiceType.put(plmn, listServiceType);
+ }
+ logd("getSatelliteCarrierConfigUpdateData: " + "subId: " + mSubId + ": "
+ + mapPlmnServiceType);
+ return "subId: " + mSubId + ": " + mapPlmnServiceType;
+ }
+
+ private String getSatelliteAllowAccess() {
+ logd("getSatelliteAllowAccess");
+ return Boolean.toString(mSatelliteAccessController.isSatelliteAllowAccessControl());
+ }
+
+ private String getSatelliteConfigCountryCodes() {
+ logd("getSatelliteConfigCountryCodes");
+ return String.join(",", mSatelliteAccessController.getSatelliteCountryCodes());
+ }
+
+ private String getSatelliteConfigJsonFile(Context context) {
+ logd("getSatelliteConfigJsonFile");
+
+ File jsonFile = mSatelliteAccessController.getSatelliteAccessConfigFile();
+ if (jsonFile == null) {
+ loge("getSatelliteConfigJsonFile: satellite access config json file is null");
+ return "satellite access config json file is not ready";
+ }
+ return SatelliteAccessConfigurationParser
+ .readJsonStringFromFile(jsonFile.getAbsolutePath());
+ }
+
+ private String getSatelliteS2SatFileSize(Context context) {
+ logd("getSatelliteS2SatFileSize");
+ File s2CellFile = mSatelliteAccessController.getSatelliteS2CellFile();
+ if (s2CellFile == null) {
+ loge("getSatelliteS2SatFileSize: s2satFile is null");
+ return "s2satFile is null";
+ }
+ return Long.toString(s2CellFile.length());
+ }
+
+ @NonNull
+ private static String[] readStringArrayFromOverlayConfig(
+ @NonNull Context context, @ArrayRes int id) {
+ String[] strArray = null;
+ try {
+ strArray = context.getResources().getStringArray(id);
+ } catch (Resources.NotFoundException ex) {
+ loge("readStringArrayFromOverlayConfig: id= " + id + ", ex=" + ex);
+ }
+ if (strArray == null) {
+ strArray = new String[0];
+ }
+ return strArray;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@androidx.annotation.NonNull MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private static void logd(@NonNull String log) {
+ Log.d(TAG, log);
+ }
+
+ private static void loge(@NonNull String log) {
+ Log.e(TAG, log);
+ }
+}
diff --git a/src/com/android/phone/settings/VoicemailSettingsActivity.java b/src/com/android/phone/settings/VoicemailSettingsActivity.java
index 909a3ad..baae26b 100644
--- a/src/com/android/phone/settings/VoicemailSettingsActivity.java
+++ b/src/com/android/phone/settings/VoicemailSettingsActivity.java
@@ -264,6 +264,8 @@
NotificationChannelController.CHANNEL_ID_VOICE_MAIL);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, mPhone.getContext().getPackageName());
mVoicemailNotificationPreference.setIntent(intent);
+
+ SettingsConstants.setupEdgeToEdge(this);
}
@Override
@@ -289,6 +291,10 @@
mPreviousVMProviderKey = mVoicemailProviders.getValue();
mVoicemailSettings = (PreferenceScreen) findPreference(BUTTON_VOICEMAIL_SETTING_KEY);
+ // 😮💨 the legacy PreferenceScreen displays a dialog in its onClick. Set a property on the
+ // PreferenceScreen to ensure that it will fit system windows to accommodate for edge to
+ // edge.
+ mVoicemailSettings.setDialogFitsSystemWindows(true);
maybeHidePublicSettings();
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index de4076d..2222fb8 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -1398,8 +1398,7 @@
Build.VERSION.DEVICE_INITIAL_SDK_INT);
PackageManager pm = context.getPackageManager();
- if (Flags.enforceTelephonyFeatureMappingForPublicApis()
- && vendorApiLevel >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ if (vendorApiLevel >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
&& pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
sInstance = new TelecomAccountRegistry(context);
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 6860b25..4cb0575 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -126,6 +126,7 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@@ -147,11 +148,15 @@
// Timeout before we terminate the outgoing DSDA call if HOLD did not complete in time on the
// existing call.
- private static final int DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS = 2000;
+ private static final int DEFAULT_DSDA_CALL_STATE_CHANGE_TIMEOUT_MS = 5000;
// Timeout to wait for the termination of incoming call before continue with the emergency call.
private static final int DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds.
+ // Timeout to wait for ending active call on other domain before continuing with
+ // the emergency call.
+ private static final int DEFAULT_DISCONNECT_CALL_ON_OTHER_DOMAIN_TIMEOUT_MS = 2 * 1000;
+
// If configured, reject attempts to dial numbers matching this pattern.
private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
Pattern.compile("\\*228[0-9]{0,2}");
@@ -220,6 +225,8 @@
private final CdmaConferenceController mCdmaConferenceController =
new CdmaConferenceController(this);
+ private com.android.server.telecom.flags.FeatureFlags mTelecomFlags =
+ new com.android.server.telecom.flags.FeatureFlagsImpl();
private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
private ImsConferenceController mImsConferenceController;
@@ -743,6 +750,32 @@
}
}
+ private static class StateDisconnectListener extends
+ TelephonyConnection.TelephonyConnectionListener {
+ private final CompletableFuture<Boolean> mDisconnectFuture;
+
+ StateDisconnectListener(CompletableFuture<Boolean> future) {
+ mDisconnectFuture = future;
+ }
+
+ @Override
+ public void onStateChanged(
+ Connection connection, @Connection.ConnectionState int state) {
+ TelephonyConnection c = (TelephonyConnection) connection;
+ if (c != null) {
+ switch (c.getState()) {
+ case Connection.STATE_DISCONNECTED: {
+ Log.d(LOG_TAG, "Connection " + connection.getTelecomCallId()
+ + " changed to STATE_DISCONNECTED!");
+ mDisconnectFuture.complete(true);
+ c.removeTelephonyConnectionListener(this);
+ }
+ break;
+ }
+ }
+ }
+ }
+
private static class OnDisconnectListener extends
com.android.internal.telephony.Connection.ListenerBase {
private final CompletableFuture<Boolean> mFuture;
@@ -1337,7 +1370,11 @@
}
return resultConnection;
} else {
- if (mTelephonyManagerProxy.isConcurrentCallsPossible()) {
+ // If call sequencing is enabled, Telecom will take care of holding calls across
+ // subscriptions if needed before delegating the connection creation over to
+ // Telephony.
+ if (mTelephonyManagerProxy.isConcurrentCallsPossible()
+ && !mTelecomFlags.enableCallSequencing()) {
Conferenceable c = maybeHoldCallsOnOtherSubs(request.getAccountHandle());
if (c != null) {
delayDialForOtherSubHold(phone, c, (success) -> {
@@ -1367,44 +1404,61 @@
}
}
- CompletableFuture<Void> maybeHoldFuture =
- checkAndHoldCallsOnOtherSubsForEmergencyCall(request,
+ CompletableFuture<Void> maybeHoldOrDisconnectOnOtherSubsFuture =
+ checkAndHoldOrDisconnectCallsOnOtherSubsForEmergencyCall(request,
resultConnection, phone);
Consumer<Boolean> ddsSwitchConsumer = (result) -> {
Log.i(this, "onCreateOutgoingConn emergency-"
+ " delayDialForDdsSwitch result = " + result);
placeOutgoingConnection(request, resultConnection, phone);
};
- maybeHoldFuture.thenRun(() -> delayDialForDdsSwitch(phone, ddsSwitchConsumer));
+ maybeHoldOrDisconnectOnOtherSubsFuture.thenRun(() -> delayDialForDdsSwitch(phone,
+ ddsSwitchConsumer));
return resultConnection;
}
}
}
- private CompletableFuture<Void> checkAndHoldCallsOnOtherSubsForEmergencyCall(
+ private CompletableFuture<Void> checkAndHoldOrDisconnectCallsOnOtherSubsForEmergencyCall(
ConnectionRequest request, Connection resultConnection, Phone phone) {
- CompletableFuture<Void> maybeHoldFuture = CompletableFuture.completedFuture(null);
- if (mTelephonyManagerProxy.isConcurrentCallsPossible()
- && shouldHoldForEmergencyCall(phone)) {
+ CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
+ if (mTelephonyManagerProxy.isConcurrentCallsPossible()) {
// If the PhoneAccountHandle was adjusted on building the TelephonyConnection,
// the relevant PhoneAccountHandle will be updated in resultConnection.
PhoneAccountHandle phoneAccountHandle =
resultConnection.getPhoneAccountHandle() == null
- ? request.getAccountHandle() : resultConnection.getPhoneAccountHandle();
- Conferenceable c = maybeHoldCallsOnOtherSubs(phoneAccountHandle);
- if (c != null) {
- maybeHoldFuture = delayDialForOtherSubHold(phone, c, (success) -> {
- Log.i(this, "checkAndHoldCallsOnOtherSubsForEmergencyCall"
- + " delayDialForOtherSubHold success = " + success);
- if (!success) {
- // Terminates the existing call to make way for the emergency call.
- hangup(c, android.telephony.DisconnectCause
- .OUTGOING_EMERGENCY_CALL_PLACED);
- }
- });
+ ? request.getAccountHandle()
+ : resultConnection.getPhoneAccountHandle();
+ if (shouldHoldForEmergencyCall(phone) && !mTelecomFlags.enableCallSequencing()) {
+ Conferenceable c = maybeHoldCallsOnOtherSubs(phoneAccountHandle);
+ if (c != null) {
+ future = delayDialForOtherSubHold(phone, c, (success) -> {
+ Log.i(this, "checkAndHoldOrDisconnectCallsOnOtherSubsForEmergencyCall"
+ + " delayDialForOtherSubHold success = " + success);
+ if (!success) {
+ // Terminates the existing call to make way for the emergency call.
+ hangup(c, android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ }
+ });
+ }
+ } else {
+ Log.i(this, "checkAndHoldOrDisconnectCallsOnOtherSubsForEmergencyCall"
+ + " disconnectAllCallsOnOtherSubs, phoneAccountExcluded: "
+ + phoneAccountHandle);
+ // Disconnect any calls on other subscription as part of call sequencing. This will
+ // cover the shared data call case too when we have a call on the shared data sim
+ // as the call will always try to be placed on the sim in service. Refer to
+ // #isAvailableForEmergencyCalls.
+ List<Conferenceable> disconnectedConferenceables =
+ disconnectAllConferenceablesOnOtherSubs(phoneAccountHandle);
+ future = delayDialForOtherSubDisconnects(phone, disconnectedConferenceables,
+ (success) -> Log.i(this,
+ "checkAndHoldOrDisconnectCallsOnOtherSubsForEmergencyCall"
+ + " delayDialForOtherSubDisconnects success = " + success));
}
}
- return maybeHoldFuture;
+ return future;
}
private Connection placeOutgoingConnection(ConnectionRequest request,
@@ -2677,10 +2731,12 @@
phone);
}
- CompletableFuture<Void> maybeHoldFuture =
- checkAndHoldCallsOnOtherSubsForEmergencyCall(request, resultConnection, phone);
- maybeHoldFuture.thenRun(() -> placeEmergencyConnectionInternal(resultConnection,
- phone, request, numberToDial, isTestEmergencyNumber, needToTurnOnRadio));
+ CompletableFuture<Void> maybeHoldOrDisconnectOnOtherSubFuture =
+ checkAndHoldOrDisconnectCallsOnOtherSubsForEmergencyCall(request,
+ resultConnection, phone);
+ maybeHoldOrDisconnectOnOtherSubFuture.thenRun(() -> placeEmergencyConnectionInternal(
+ resultConnection, phone, request, numberToDial, isTestEmergencyNumber,
+ needToTurnOnRadio));
// Non TelephonyConnection type instance means dialing failure.
return resultConnection;
@@ -2803,11 +2859,142 @@
+ "reject incoming, dialing canceled");
return;
}
- placeEmergencyConnectionOnSelectedDomain(request, resultConnection, phone);
+ // Hang up the active calls if the domain of currently active call is different
+ // from the domain selected by domain selector.
+ if (Flags.hangupActiveCallBasedOnEmergencyCallDomain()) {
+ CompletableFuture<Void> disconnectCall = maybeDisconnectCallsOnOtherDomain(
+ phone, resultConnection, result,
+ getAllConnections(), getAllConferences(), (ret) -> {
+ if (!ret) {
+ Log.i(this, "createEmergencyConnection: "
+ + "disconnecting call on other domain failed");
+ }
+ });
+
+ CompletableFuture<Void> unused = disconnectCall.thenRun(() -> {
+ if (resultConnection.getState() == Connection.STATE_DISCONNECTED) {
+ Log.i(this, "createEmergencyConnection: "
+ + "disconnect call on other domain, dialing canceled");
+ return;
+ }
+ placeEmergencyConnectionOnSelectedDomain(request, resultConnection, phone);
+ });
+ } else {
+ placeEmergencyConnectionOnSelectedDomain(request, resultConnection, phone);
+ }
});
}, mDomainSelectionMainExecutor);
}
+ /**
+ * Disconnect the active calls on the other domain for an emergency call.
+ * For example,
+ * - Active IMS normal call and CS emergency call
+ * - Active CS normal call and IMS emergency call
+ *
+ * @param phone The Phone to be used for an emergency call.
+ * @param emergencyConnection The connection created for an emergency call.
+ * @param emergencyDomain The selected domain for an emergency call.
+ * @param connections All individual connections, including conference participants.
+ * @param conferences All conferences.
+ * @param completeConsumer The consumer to call once the call hangup has been completed.
+ * {@code true} if the operation commpletes successfully, or
+ * {@code false} if the operation timed out/failed.
+ */
+ @VisibleForTesting
+ public static CompletableFuture<Void> maybeDisconnectCallsOnOtherDomain(Phone phone,
+ Connection emergencyConnection,
+ @NetworkRegistrationInfo.Domain int emergencyDomain,
+ @NonNull Collection<Connection> connections,
+ @NonNull Collection<Conference> conferences,
+ Consumer<Boolean> completeConsumer) {
+ List<Connection> activeConnections = connections.stream()
+ .filter(c -> {
+ return !c.equals(emergencyConnection)
+ && isConnectionOnOtherDomain(c, phone, emergencyDomain);
+ }).toList();
+ List<Conference> activeConferences = conferences.stream()
+ .filter(c -> {
+ Connection pc = c.getPrimaryConnection();
+ return isConnectionOnOtherDomain(pc, phone, emergencyDomain);
+ }).toList();
+
+ if (activeConnections.isEmpty() && activeConferences.isEmpty()) {
+ // There are no active calls.
+ completeConsumer.accept(true);
+ return CompletableFuture.completedFuture(null);
+ }
+
+ Log.i(LOG_TAG, "maybeDisconnectCallsOnOtherDomain: "
+ + "connections=" + activeConnections.size()
+ + ", conferences=" + activeConferences.size());
+
+ try {
+ CompletableFuture<Boolean> future = null;
+
+ for (Connection c : activeConnections) {
+ TelephonyConnection tc = (TelephonyConnection) c;
+ if (tc.getState() != Connection.STATE_DISCONNECTED) {
+ if (future == null) {
+ future = new CompletableFuture<>();
+ tc.getOriginalConnection().addListener(new OnDisconnectListener(future));
+ }
+ tc.hangup(android.telephony.DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED);
+ }
+ }
+
+ for (Conference c : activeConferences) {
+ if (c.getState() != Connection.STATE_DISCONNECTED) {
+ c.onDisconnect();
+ }
+ }
+
+ if (future != null) {
+ // A timeout that will complete the future to not block the outgoing call
+ // indefinitely.
+ CompletableFuture<Boolean> timeout = new CompletableFuture<>();
+ phone.getContext().getMainThreadHandler().postDelayed(
+ () -> timeout.complete(false),
+ DEFAULT_DISCONNECT_CALL_ON_OTHER_DOMAIN_TIMEOUT_MS);
+ // Ensure that the Consumer is completed on the main thread.
+ return future.acceptEitherAsync(timeout, completeConsumer,
+ phone.getContext().getMainExecutor()).exceptionally((ex) -> {
+ Log.w(LOG_TAG, "maybeDisconnectCallsOnOtherDomain: exceptionally="
+ + ex);
+ return null;
+ });
+ } else {
+ completeConsumer.accept(true);
+ return CompletableFuture.completedFuture(null);
+ }
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "maybeDisconnectCallsOnOtherDomain: exception=" + e.getMessage());
+ completeConsumer.accept(false);
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ private static boolean isConnectionOnOtherDomain(Connection c, Phone phone,
+ @NetworkRegistrationInfo.Domain int domain) {
+ if (c instanceof TelephonyConnection) {
+ TelephonyConnection tc = (TelephonyConnection) c;
+ Phone callPhone = tc.getPhone();
+ int callDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
+
+ if (callPhone != null && callPhone.getSubId() == phone.getSubId()) {
+ if (tc.isGsmCdmaConnection()) {
+ callDomain = NetworkRegistrationInfo.DOMAIN_CS;
+ } else if (tc.isImsConnection()) {
+ callDomain = NetworkRegistrationInfo.DOMAIN_PS;
+ }
+ }
+
+ return callDomain != NetworkRegistrationInfo.DOMAIN_UNKNOWN
+ && callDomain != domain;
+ }
+ return false;
+ }
+
private void dialCsEmergencyCall(final Phone phone,
final TelephonyConnection resultConnection, final ConnectionRequest request) {
Log.d(this, "dialCsEmergencyCall");
@@ -3831,7 +4018,7 @@
// a timeout that will complete the future to not block the outgoing call indefinitely.
CompletableFuture<Boolean> timeout = new CompletableFuture<>();
phone.getContext().getMainThreadHandler().postDelayed(
- () -> timeout.complete(false), DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS);
+ () -> timeout.complete(false), DEFAULT_DSDA_CALL_STATE_CHANGE_TIMEOUT_MS);
// Ensure that the Consumer is completed on the main thread.
return stateHoldingFuture.acceptEitherAsync(timeout, completeConsumer,
phone.getContext().getMainExecutor());
@@ -3844,6 +4031,63 @@
}
/**
+ * For DSDA devices, block until the connections passed in are disconnected (STATE_DISCONNECTED)
+ * or time out.
+ * @return {@link CompletableFuture} indicating the completion result after performing
+ * the bulk disconnect
+ */
+ private CompletableFuture<Void> delayDialForOtherSubDisconnects(Phone phone,
+ List<Conferenceable> conferenceables, Consumer<Boolean> completeConsumer) {
+ if (conferenceables.isEmpty()) {
+ completeConsumer.accept(true);
+ return CompletableFuture.completedFuture(null);
+ }
+ if (phone == null) {
+ // Unexpected inputs
+ completeConsumer.accept(false);
+ return CompletableFuture.completedFuture(null);
+ }
+ List<CompletableFuture<Void>> disconnectFutures = new ArrayList<>();
+ for (Conferenceable conferenceable : conferenceables) {
+ CompletableFuture<Void> disconnectFuture = CompletableFuture.completedFuture(null);
+ try {
+ if (conferenceable == null) {
+ disconnectFuture = CompletableFuture.completedFuture(null);
+ } else {
+ // Listen for each disconnect as part of an individual future.
+ disconnectFuture = CompletableFuture.runAsync(() ->
+ listenForDisconnectStateChanged(conferenceable)
+ .completeOnTimeout(false,
+ DEFAULT_DSDA_CALL_STATE_CHANGE_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS),
+ phone.getContext().getMainExecutor());
+ }
+ } catch (Exception e) {
+ Log.w(this, "delayDialForOtherSubDisconnects - exception= " + e.getMessage());
+ disconnectFuture = CompletableFuture.completedFuture(null);
+ } finally {
+ disconnectFutures.add(disconnectFuture);
+ }
+ }
+ // Return a future that waits for all the disconnect futures to complete.
+ return CompletableFuture.allOf(disconnectFutures.toArray(CompletableFuture[]::new));
+ }
+
+ /**
+ * Listen for the disconnect state change from the passed in {@link Conferenceable}.
+ * @param conferenceable
+ * @return {@link CompletableFuture} that provides the result of waiting on the
+ * disconnect state change.
+ */
+ private CompletableFuture<Boolean> listenForDisconnectStateChanged(
+ @NonNull Conferenceable conferenceable) {
+ CompletableFuture<Boolean> future = new CompletableFuture<>();
+ final StateDisconnectListener disconnectListener = new StateDisconnectListener(future);
+ addTelephonyConnectionListener(conferenceable, disconnectListener);
+ return future;
+ }
+
+ /**
* If needed, block until an incoming call is disconnected for outgoing emergency call,
* or timeout expires.
* @param phone The Phone to reject the incoming call
@@ -4512,6 +4756,8 @@
*/
public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection,
@NonNull PhoneAccountHandle phoneAccountHandle) {
+ // With sequencing, Telecom handles setting the extra.
+ if (mTelecomFlags.enableCallSequencing()) return;
if (isCallPresentOnOtherSub(phoneAccountHandle)) {
if (mTelephonyManagerProxy.isConcurrentCallsPossible()
&& allCallsSupportHold(connection)) {
@@ -4775,8 +5021,47 @@
return null;
}
- private void disconnectAllCallsOnOtherSubs (@NonNull PhoneAccountHandle handle) {
- Collection<Connection>connections = getAllConnections();
+ /**
+ * For DSDA devices, disconnects all calls (and conferences) on other subs when placing an
+ * emergency call.
+ * @param handle The {@link PhoneAccountHandle} to exclude when disconnecting calls
+ * @return {@link List} compromised of the conferenceables that have been disconnected.
+ */
+ @VisibleForTesting
+ protected List<Conferenceable> disconnectAllConferenceablesOnOtherSubs(
+ @NonNull PhoneAccountHandle handle) {
+ List<Conferenceable> conferenceables = new ArrayList<>();
+ Collection<Conference> conferences = getAllConferences();
+ // Add the conferences
+ conferences.stream()
+ .filter(c ->
+ (c.getState() == Connection.STATE_ACTIVE
+ || c.getState() == Connection.STATE_HOLDING)
+ // Include any calls not on same sub as current connection.
+ && !Objects.equals(c.getPhoneAccountHandle(), handle))
+ .forEach(c -> {
+ if (c instanceof TelephonyConference) {
+ TelephonyConference tc = (TelephonyConference) c;
+ Log.i(LOG_TAG, "disconnectAllConferenceablesOnOtherSubs: disconnect"
+ + " %s due to redial happened on other sub.",
+ tc.getTelecomCallId());
+ tc.onDisconnect();
+ conferenceables.add(c);
+ }
+ });
+ // Add the connections.
+ conferenceables.addAll(disconnectAllCallsOnOtherSubs(handle));
+ return conferenceables;
+ }
+
+ /**
+ * For DSDA devices, disconnects all calls on other subs when placing an emergency call.
+ * @param handle The {@link PhoneAccountHandle} to exclude when disconnecting calls
+ * @return {@link List} including compromised of the connections that have been disconnected.
+ */
+ private List<Connection> disconnectAllCallsOnOtherSubs(@NonNull PhoneAccountHandle handle) {
+ Collection<Connection> connections = getAllConnections();
+ List<Connection> disconnectedConnections = new ArrayList<>();
connections.stream()
.filter(c ->
(c.getState() == Connection.STATE_ACTIVE
@@ -4790,8 +5075,10 @@
" %s due to redial happened on other sub.",
tc.getTelecomCallId());
tc.hangup(android.telephony.DisconnectCause.LOCAL);
+ disconnectedConnections.add(c);
}
});
+ return disconnectedConnections;
}
private @NetworkRegistrationInfo.Domain int getActiveCallDomain(int subId) {
@@ -4922,8 +5209,10 @@
/* Only for testing */
@VisibleForTesting
- public void setFeatureFlags(FeatureFlags featureFlags) {
+ public void setFeatureFlags(FeatureFlags featureFlags,
+ com.android.server.telecom.flags.FeatureFlags telecomFlags) {
mFeatureFlags = featureFlags;
+ mTelecomFlags = telecomFlags;
}
private void loge(String s) {
diff --git a/testapps/TestSatelliteApp/AndroidManifest.xml b/testapps/TestSatelliteApp/AndroidManifest.xml
index 1825df2..de455f2 100644
--- a/testapps/TestSatelliteApp/AndroidManifest.xml
+++ b/testapps/TestSatelliteApp/AndroidManifest.xml
@@ -17,7 +17,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.phone.testapps.satellitetestapp">
- <application android:label="SatelliteTestApp">
+ <application
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:label="SatelliteTestApp">
<activity
android:name=".SatelliteTestApp"
android:exported="true"
@@ -55,10 +57,17 @@
<action android:name="android.telephony.satellite.SatelliteService" />
</intent-filter>
</service>
+
+ <meta-data
+ android:name="android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED"
+ android:value="true"/>
</application>
<uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.BIND_SATELLITE_SERVICE" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.INTERNET" />
</manifest>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
index 26b45e3..43cce9b 100644
--- a/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
+++ b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
@@ -81,5 +81,12 @@
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:text="@string/TestSatelliteWrapper"/>
+ <Button
+ android:id="@+id/TestSatelliteConstrainConnection"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:text="@string/TestSatelliteConstrainConnection"/>
</LinearLayout>
</ScrollView>
diff --git a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
index 5c3a72d..f48c022 100644
--- a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
+++ b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
@@ -62,6 +62,8 @@
<string name="sendMessage">sendMessage</string>
<string name="receiveMessage">receiveMessage</string>
+ <string name="TestSatelliteConstrainConnection">Test Satellite Constrain Connection</string>
+
<string name="TestSatelliteWrapper">Test Satellite Wrapper</string>
<string name="requestNtnSignalStrength">requestNtnSignalStrength</string>
<string name="registerForNtnSignalStrengthChanged">registerForNtnSignalStrengthChanged</string>
diff --git a/testapps/TestSatelliteApp/res/xml/network_security_config.xml b/testapps/TestSatelliteApp/res/xml/network_security_config.xml
new file mode 100644
index 0000000..463e65a
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/xml/network_security_config.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">www.google.com</domain>
+ </domain-config>
+</network-security-config>
\ No newline at end of file
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/PingTask.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/PingTask.java
new file mode 100644
index 0000000..fe86c21
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/PingTask.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 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.phone.testapps.satellitetestapp;
+
+import android.net.Network;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Scanner;
+
+class PingTask extends AsyncTask<Network, Integer, Integer> {
+ protected Integer doInBackground(Network... network) {
+ ping(network[0]);
+ return 0;
+ }
+ String ping(Network network) {
+ URL url = null;
+ try {
+ url = new URL("http://www.google.com");
+ } catch (Exception e) {
+ Log.d("SatelliteDataConstrained", "exception: " + e);
+ }
+ if (url != null) {
+ try {
+ Log.d("SatelliteDataConstrained", "ping " + url);
+ String result = httpGet(network, url);
+ Log.d("SatelliteDataConstrained", "Ping Success");
+ return result;
+ } catch (Exception e) {
+ Log.d("SatelliteDataConstrained", "exception: " + e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Performs a HTTP GET to the specified URL on the specified Network, and returns
+ * the response body decoded as UTF-8.
+ */
+ private static String httpGet(Network network, URL httpUrl) throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) network.openConnection(httpUrl);
+ try {
+ InputStream inputStream = connection.getInputStream();
+ Log.d("httpGet", "httpUrl + " + httpUrl);
+ Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
+ return scanner.hasNext() ? scanner.next() : "";
+ } finally {
+ connection.disconnect();
+ }
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
index cb56e87..911e179 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
@@ -23,15 +23,24 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.os.Bundle;
+import android.os.Looper;
import android.os.IBinder;
import android.telephony.satellite.stub.SatelliteDatagram;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
+import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* SatelliteTestApp main activity to navigate to other APIs related to satellite.
@@ -41,14 +50,23 @@
private static final String TAG = "SatelliteTestApp";
public static TestSatelliteService sSatelliteService;
private final Object mSendDatagramLock = new Object();
-
+ Network mNetwork = null;
+ Context mContext;
+ ConnectivityManager mConnectivityManager;
+ NetworkCallback mSatelliteConstrainNetworkCallback;
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
private TestSatelliteServiceConnection mSatelliteServiceConn;
private List<SatelliteDatagram> mSentSatelliteDatagrams = new ArrayList<>();
private static final int REQUEST_CODE_SEND_SMS = 1;
+ private final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37;
+ private boolean isNetworkRequested = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mContext = getApplicationContext();
+
+ mConnectivityManager = getSystemService(ConnectivityManager.class);
if (mSatelliteServiceConn == null) {
mSatelliteServiceConn = new TestSatelliteServiceConnection();
@@ -106,6 +124,21 @@
startActivity(intent);
}
});
+
+ findViewById(R.id.TestSatelliteConstrainConnection).setOnClickListener(view -> {
+ executor.execute(() -> {
+ Log.e(TAG, "onClick");
+ mSatelliteConstrainNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(final Network network) {
+ makeSatelliteDataConstrainedPing(network);
+ }
+ };
+ if(isNetworkRequested == false) {
+ requestingNetwork();
+ }
+ });
+ });
}
@Override
@@ -117,6 +150,61 @@
}
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if(isNetworkRequested == true) {
+ releasingNetwork();
+ }
+ }
+
+ private void requestingNetwork() {
+ Log.e(TAG, "Requesting Network");
+ isNetworkRequested = true;
+ NetworkRequest request = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+ .build();
+
+ // Requesting for Network
+ mConnectivityManager.requestNetwork(request, mSatelliteConstrainNetworkCallback);
+ Log.e(TAG, "onClick + " + request);
+ }
+
+
+ private void makeSatelliteDataConstrainedPing(final Network network) {
+ Log.e(TAG, "onAvailable + " + network);
+ mNetwork = network;
+
+ try {
+ PingTask pingTask = new PingTask();
+ Log.d(TAG, "Connecting Satellite for ping");
+ String pingResult = pingTask.ping(mNetwork);
+ if(pingResult != null) {
+ Toast.makeText(mContext, "Ping Passed!", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(mContext, "Ping Failed!", Toast.LENGTH_SHORT).show();
+ }
+ } catch (Exception e) {
+ Log.d(TAG, "Exception at ping: " + e);
+ } finally {
+ // Releasing the callback in the background thread
+ releasingNetwork();
+ }
+ }
+
+ private void releasingNetwork() {
+ Log.e(TAG, "Realsing Network");
+ try {
+ mConnectivityManager
+ .unregisterNetworkCallback(mSatelliteConstrainNetworkCallback);
+ } catch (Exception e) {
+ Log.d("SatelliteDataConstrined", "Exception: " + e);
+ }
+ isNetworkRequested = false;
+ }
private final ILocalSatelliteListener mSatelliteListener =
new ILocalSatelliteListener.Stub() {
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
index 94e91d3..d8c3727 100644
--- a/tests/src/com/android/TelephonyTestBase.java
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -24,7 +24,9 @@
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
+import android.os.TestLooperManager;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -37,6 +39,7 @@
import com.android.internal.telephony.data.DataNetworkController;
import com.android.internal.telephony.metrics.MetricsCollector;
import com.android.internal.telephony.metrics.PersistAtomsStorage;
+import com.android.internal.telephony.satellite.SatelliteController;
import com.android.phone.PhoneGlobals;
import com.android.phone.PhoneInterfaceManager;
@@ -69,6 +72,10 @@
@Mock protected DataNetworkController mDataNetworkController;
@Mock private MetricsCollector mMetricsCollector;
+ private HandlerThread mTestHandlerThread;
+ protected Looper mTestLooper;
+ protected TestLooperManager mLooperManager;
+
private final HashMap<InstanceKey, Object> mOldInstances = new HashMap<>();
private final LinkedList<InstanceKey> mInstanceKeys = new LinkedList<>();
@@ -80,6 +87,9 @@
doCallRealMethod().when(mPhoneGlobals).getBaseContext();
doCallRealMethod().when(mPhoneGlobals).getResources();
+ doCallRealMethod().when(mPhoneGlobals).getSystemService(Mockito.anyString());
+ doCallRealMethod().when(mPhoneGlobals).getSystemService(Mockito.any(Class.class));
+ doCallRealMethod().when(mPhoneGlobals).getSystemServiceName(Mockito.any(Class.class));
doCallRealMethod().when(mPhone).getServiceState();
mContext = spy(new TestContext());
@@ -96,6 +106,8 @@
replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
replaceInstance(PhoneGlobals.class, "sMe", null, mPhoneGlobals);
replaceInstance(PhoneFactory.class, "sMetricsCollector", null, mMetricsCollector);
+ replaceInstance(SatelliteController.class, "sInstance", null,
+ Mockito.mock(SatelliteController.class));
doReturn(Mockito.mock(PersistAtomsStorage.class)).when(mMetricsCollector).getAtomsStorage();
@@ -112,9 +124,47 @@
public void tearDown() throws Exception {
// Ensure there are no static references to handlers after test completes.
PhoneConfigurationManager.unregisterAllMultiSimConfigChangeRegistrants();
+ cleanupTestLooper();
restoreInstances();
}
+ protected void setupTestLooper() {
+ mTestHandlerThread = new HandlerThread("TestHandlerThread");
+ mTestHandlerThread.start();
+ mTestLooper = mTestHandlerThread.getLooper();
+ mLooperManager = new TestLooperManager(mTestLooper);
+ }
+
+ private void cleanupTestLooper() {
+ mTestLooper = null;
+ if (mLooperManager != null) {
+ mLooperManager.release();
+ mLooperManager = null;
+ }
+ if (mTestHandlerThread != null) {
+ mTestHandlerThread.quit();
+ try {
+ mTestHandlerThread.join();
+ } catch (InterruptedException ex) {
+ Log.w("TelephonyTestBase", "HandlerThread join interrupted", ex);
+ }
+ mTestHandlerThread = null;
+ }
+ }
+
+ protected void processOneMessage() {
+ var msg = mLooperManager.next();
+ mLooperManager.execute(msg);
+ mLooperManager.recycle(msg);
+ }
+
+ protected void processAllMessages() {
+ for (var msg = mLooperManager.poll(); msg != null && msg.getTarget() != null;) {
+ mLooperManager.execute(msg);
+ mLooperManager.recycle(msg);
+ }
+ }
+
protected final boolean waitForExecutorAction(Executor executor, long timeoutMillis) {
final CountDownLatch lock = new CountDownLatch(1);
executor.execute(() -> {
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index bf7832a..54ee6e0 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -33,6 +33,7 @@
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -72,6 +73,7 @@
@Mock ImsManager mMockImsManager;
@Mock UserManager mMockUserManager;
@Mock PackageManager mPackageManager;
+ @Mock ConnectivityManager mMockConnectivityManager;
private final SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
@@ -192,6 +194,9 @@
case Context.CARRIER_CONFIG_SERVICE: {
return mMockCarrierConfigManager;
}
+ case Context.CONNECTIVITY_SERVICE: {
+ return mMockConnectivityManager;
+ }
case Context.TELECOM_SERVICE: {
return mMockTelecomManager;
}
@@ -216,6 +221,9 @@
if (serviceClass == CarrierConfigManager.class) {
return Context.CARRIER_CONFIG_SERVICE;
}
+ if (serviceClass == ConnectivityManager.class) {
+ return Context.CONNECTIVITY_SERVICE;
+ }
if (serviceClass == TelecomManager.class) {
return Context.TELECOM_SERVICE;
}
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
index 00726c1..5b306e6 100644
--- a/tests/src/com/android/phone/CarrierConfigLoaderTest.java
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -52,7 +52,6 @@
import android.telephony.TelephonyManager;
import android.telephony.TelephonyRegistryManager;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
import androidx.test.InstrumentationRegistry;
@@ -82,7 +81,6 @@
* Unit Test for CarrierConfigLoader.
*/
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class CarrierConfigLoaderTest extends TelephonyTestBase {
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -109,7 +107,6 @@
private TelephonyManager mTelephonyManager;
private CarrierConfigLoader mCarrierConfigLoader;
private Handler mHandler;
- private TestableLooper mTestableLooper;
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
private FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();
@@ -117,6 +114,9 @@
@Before
public void setUp() throws Exception {
super.setUp();
+ setupTestLooper();
+ doReturn(true).when(mPackageManager).hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
eq(PermissionEnforcer.class));
doReturn(mFakePermissionEnforcer).when(mContext).getSystemService(
@@ -150,8 +150,7 @@
when(mContext.getSystemService(TelephonyRegistryManager.class)).thenReturn(
mTelephonyRegistryManager);
- mTestableLooper = TestableLooper.get(this);
- mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestableLooper.getLooper(),
+ mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestLooper,
mFeatureFlags);
mHandler = mCarrierConfigLoader.getHandler();
@@ -211,7 +210,10 @@
mCarrierConfigLoader.saveNoSimConfigToXml(PLATFORM_CARRIER_CONFIG_PACKAGE, config);
mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
IccCardConstants.INTENT_VALUE_ICC_ABSENT);
- mTestableLooper.processAllMessages();
+ processOneMessage();
+ processOneMessage();
+ processOneMessage();
+ processOneMessage();
assertThat(mCarrierConfigLoader.getConfigFromDefaultApp(DEFAULT_PHONE_ID)).isNull();
assertThat(mCarrierConfigLoader.getConfigFromCarrierApp(DEFAULT_PHONE_ID)).isNull();
@@ -250,7 +252,7 @@
DEFAULT_PHONE_ID, carrierId, config);
mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
IccCardConstants.INTENT_VALUE_ICC_LOADED);
- mTestableLooper.processAllMessages();
+ processAllMessages();
assertThat(mCarrierConfigLoader.getConfigFromDefaultApp(DEFAULT_PHONE_ID).getInt(
CARRIER_CONFIG_EXAMPLE_KEY)).isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
@@ -292,7 +294,8 @@
mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, null /*overrides*/,
false/*persistent*/);
- mTestableLooper.processAllMessages();
+ processOneMessage();
+ processOneMessage();
assertThat(mCarrierConfigLoader.getOverrideConfig(DEFAULT_PHONE_ID).isEmpty()).isTrue();
verify(mSubscriptionManagerService).updateSubscriptionByCarrierConfig(
@@ -314,7 +317,8 @@
PersistableBundle config = getTestConfig();
mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, config /*overrides*/,
false/*persistent*/);
- mTestableLooper.processAllMessages();
+ processOneMessage();
+ processOneMessage();
assertThat(mCarrierConfigLoader.getOverrideConfig(DEFAULT_PHONE_ID).getInt(
CARRIER_CONFIG_EXAMPLE_KEY)).isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
@@ -433,7 +437,6 @@
replaceInstance(CarrierConfigLoader.class, "mVendorApiLevel", mCarrierConfigLoader,
vendorApiLevel);
- doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
doReturn(false).when(mPackageManager).hasSystemFeature(
eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
@@ -479,10 +482,10 @@
any(Intent.class), any(ServiceConnection.class), anyInt());
doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
mHandler.sendMessage(mHandler.obtainMessage(17 /* EVENT_MULTI_SIM_CONFIG_CHANGED */));
- mTestableLooper.processAllMessages();
+ processAllMessages();
mCarrierConfigLoader.updateConfigForPhoneId(1, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
- mTestableLooper.processAllMessages();
+ processAllMessages();
}
@Test
@@ -502,18 +505,20 @@
Mockito.clearInvocations(mTelephonyRegistryManager);
Mockito.clearInvocations(mContext);
mHandler.sendMessage(mHandler.obtainMessage(13 /* EVENT_SYSTEM_UNLOCKED */));
- mTestableLooper.processAllMessages();
+ processOneMessage();
mHandler.sendMessage(mHandler.obtainMessage(5 /* EVENT_FETCH_DEFAULT_DONE */));
- mTestableLooper.processAllMessages();
+ processOneMessage();
+ processOneMessage();
mHandler.sendMessage(mHandler.obtainMessage(6 /* EVENT_FETCH_CARRIER_DONE */));
- mTestableLooper.processAllMessages();
+ processOneMessage();
+ processOneMessage();
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mSubscriptionManagerService).updateSubscriptionByCarrierConfig(eq(0), anyString(),
any(PersistableBundle.class), runnableCaptor.capture());
runnableCaptor.getValue().run();
- mTestableLooper.processAllMessages();
+ processAllMessages();
// Broadcast should be sent for backwards compatibility.
verify(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
diff --git a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
index 5521ac0..0c91af7 100644
--- a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
+++ b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
@@ -985,7 +985,9 @@
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subIds);
}
- private void processAllMessages() {
+ // Override - not using mTestLooper from the base class
+ @Override
+ protected void processAllMessages() {
while (!mLooper.getLooper().getQueue().isIdle()) {
mLooper.processAllMessages();
}
diff --git a/tests/src/com/android/phone/LocationAccessPolicyTest.java b/tests/src/com/android/phone/LocationAccessPolicyTest.java
index 551c2cb..7acdb77 100644
--- a/tests/src/com/android/phone/LocationAccessPolicyTest.java
+++ b/tests/src/com/android/phone/LocationAccessPolicyTest.java
@@ -148,7 +148,7 @@
}
}
- private static final int TESTING_UID = 10001;
+ private static final int TESTING_UID = UserHandle.getUid(UserHandle.myUserId(), 10001);
private static final int TESTING_PID = 8009;
private static final String TESTING_CALLING_PACKAGE = "com.android.phone";
diff --git a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
index bbcb52b..266481e 100644
--- a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
+++ b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
@@ -59,6 +59,7 @@
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.SatelliteController;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.satellite.accesscontrol.SatelliteAccessController;
@@ -118,6 +119,9 @@
replaceInstance(SatelliteAccessController.class, "sInstance", null,
Mockito.mock(SatelliteAccessController.class));
+ replaceInstance(SatelliteController.class, "sInstance", null,
+ Mockito.mock(SatelliteController.class));
+
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(
InstrumentationRegistry.getInstrumentation().getTargetContext());
doReturn(mSharedPreferences).when(mPhoneGlobals)
@@ -142,7 +146,6 @@
// In order not to affect the existing implementation, define a telephony features
// and disabled enforce_telephony_feature_mapping_for_public_apis feature flag
mPhoneInterfaceManager.setFeatureFlags(mFeatureFlags);
- doReturn(false).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
doReturn(true).when(mFeatureFlags).hsumPackageManager();
mPhoneInterfaceManager.setPackageManager(mPackageManager);
doReturn(mPackageManager).when(mPhoneGlobals).getPackageManager();
@@ -508,7 +511,6 @@
@Test
@EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
public void testWithTelephonyFeatureAndCompatChanges() throws Exception {
- doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
mPhoneInterfaceManager.setFeatureFlags(mFeatureFlags);
doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
@@ -533,7 +535,6 @@
doReturn(false).when(mPackageManager).hasSystemFeature(
PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS);
mPhoneInterfaceManager.setPackageManager(mPackageManager);
- doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
mPhoneInterfaceManager.setFeatureFlags(mFeatureFlags);
doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
diff --git a/tests/src/com/android/phone/SimPhonebookProviderTest.java b/tests/src/com/android/phone/SimPhonebookProviderTest.java
index d8518f8..817c53e 100644
--- a/tests/src/com/android/phone/SimPhonebookProviderTest.java
+++ b/tests/src/com/android/phone/SimPhonebookProviderTest.java
@@ -29,11 +29,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
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.ContentResolver;
import android.content.ContentValues;
@@ -169,8 +169,7 @@
@Test
public void query_entityFiles_noSim_returnsEmptyCursor() {
- when(mMockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- ImmutableList.of());
+ doReturn(ImmutableList.of()).when(mMockSubscriptionManager).getActiveSubscriptionInfoList();
try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null, null)) {
assertThat(cursor).hasCount(0);
@@ -363,7 +362,7 @@
// Use a mock so that a null list can be returned
IIccPhoneBook mockIccPhoneBook = mock(
IIccPhoneBook.class, AdditionalAnswers.delegatesTo(mIccPhoneBook));
- when(mockIccPhoneBook.getAdnRecordsInEfForSubscriber(anyInt(), anyInt())).thenReturn(null);
+ doReturn(null).when(mockIccPhoneBook).getAdnRecordsInEfForSubscriber(anyInt(), anyInt());
TestableSimPhonebookProvider.setup(mResolver, mMockSubscriptionManager, mockIccPhoneBook);
try (Cursor adnCursor = mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null,
@@ -1334,14 +1333,14 @@
}
private void setupSimsWithSubscriptionIds(int... subscriptionIds) {
- when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subscriptionIds);
- when(mMockSubscriptionManager.getActiveSubscriptionInfoCount())
- .thenReturn(subscriptionIds.length);
+ doReturn(subscriptionIds).when(mMockSubscriptionManager).getActiveSubscriptionIdList();
+ doReturn(subscriptionIds.length).when(mMockSubscriptionManager)
+ .getActiveSubscriptionInfoCount();
List<SubscriptionInfo> subscriptions = createSubscriptionsWithIds(subscriptionIds);
- when(mMockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(subscriptions);
+ doReturn(subscriptions).when(mMockSubscriptionManager).getActiveSubscriptionInfoList();
for (SubscriptionInfo info : subscriptions) {
- when(mMockSubscriptionManager.getActiveSubscriptionInfo(info.getSubscriptionId()))
- .thenReturn(info);
+ doReturn(info).when(mMockSubscriptionManager)
+ .getActiveSubscriptionInfo(info.getSubscriptionId());
}
}
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
index 8c97ab7..8df197c 100644
--- a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
@@ -126,6 +126,7 @@
import com.android.internal.telephony.satellite.SatelliteConfigParser;
import com.android.internal.telephony.satellite.SatelliteController;
import com.android.internal.telephony.satellite.SatelliteModemInterface;
+import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteControllerStats;
import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -243,6 +244,8 @@
@Mock
private ConcurrentHashMap<IBinder, ISatelliteCommunicationAccessStateCallback>
mMockSatelliteCommunicationAccessStateChangedListeners;
+ @Mock
+ private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats;
private SatelliteInfo mSatelliteInfo;
@@ -366,6 +369,8 @@
mMockCountryDetector);
replaceInstance(ControllerMetricsStats.class, "sInstance", null,
mock(ControllerMetricsStats.class));
+ replaceInstance(CarrierRoamingSatelliteControllerStats.class, "sInstance", null,
+ mCarrierRoamingSatelliteControllerStats);
when(mMockSatelliteController.getSatellitePhone()).thenReturn(mMockPhone);
when(mMockPhone.getSubId()).thenReturn(SubscriptionManager.getDefaultSubscriptionId());
@@ -444,6 +449,7 @@
mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
when(mMockPackageManager.getApplicationInfo(anyString(), anyInt()))
.thenReturn(mMockApplicationInfo);
+ when(mCarrierRoamingSatelliteControllerStats.isMultiSim()).thenReturn(false);
mSatelliteInfo = new SatelliteInfo(
UUID.randomUUID(),
@@ -1761,7 +1767,6 @@
mSatelliteAccessControllerUT,
DEFAULT_S2_LEVEL);
when(mMockFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
- when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getBoolean(
com.android.internal.R.bool.config_oem_enabled_satellite_access_allow))
diff --git a/tests/src/com/android/phone/security/SafetySourceReceiverTest.java b/tests/src/com/android/phone/security/SafetySourceReceiverTest.java
index 584fb25..7268771 100644
--- a/tests/src/com/android/phone/security/SafetySourceReceiverTest.java
+++ b/tests/src/com/android/phone/security/SafetySourceReceiverTest.java
@@ -35,7 +35,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
@@ -91,9 +90,6 @@
@Test
public void testOnReceive_noTelephonyFeature() {
- mSetFlagsRule.enableFlags(
- Flags.FLAG_ENFORCE_TELEPHONY_FEATURE_MAPPING_FOR_PUBLIC_APIS);
-
when(mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
diff --git a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index 5637c3a..a792780 100644
--- a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -40,6 +41,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
@@ -56,6 +58,7 @@
import android.telephony.data.UrspRule;
import android.testing.TestableLooper;
+import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.TelephonyTestBase;
@@ -335,6 +338,12 @@
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mHandler.obtainMessage());
mTestableLooper.processAllMessages();
+ if (isAutomotive()) {
+ // TODO(b/401032628): this test is flaky here
+ assumeTrue(
+ TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE
+ != mResult);
+ }
assertEquals(
TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
mResult);
@@ -350,6 +359,11 @@
mResult);
}
+ private boolean isAutomotive() {
+ return InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
@Test
public void testPurchasePremiumCapabilityResultNetworkNotAvailable() {
doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index c0bd2dd..2605114 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -109,6 +109,8 @@
import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.imsphone.ImsPhoneCall;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.internal.telephony.satellite.SatelliteController;
import com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -197,9 +199,15 @@
public void onHold() {
wasHeld = true;
}
+
+ @Override
+ void setOriginalConnection(com.android.internal.telephony.Connection connection) {
+ mOriginalConnection = connection;
+ }
}
public static class SimpleConference extends Conference {
+ public boolean wasDisconnected = false;
public boolean wasUnheld = false;
public SimpleConference(PhoneAccountHandle phoneAccountHandle) {
@@ -207,6 +215,11 @@
}
@Override
+ public void onDisconnect() {
+ wasDisconnected = true;
+ }
+
+ @Override
public void onUnhold() {
wasUnheld = true;
}
@@ -258,6 +271,7 @@
@Mock private EmergencyStateTracker mEmergencyStateTracker;
@Mock private Resources mMockResources;
@Mock private FeatureFlags mFeatureFlags;
+ @Mock private com.android.server.telecom.flags.FeatureFlags mTelecomFlags;
private Phone mPhone0;
private Phone mPhone1;
@@ -285,7 +299,7 @@
super.setUp();
mTestConnectionService = new TestTelephonyConnectionService(mContext);
- mTestConnectionService.setFeatureFlags(mFeatureFlags);
+ mTestConnectionService.setFeatureFlags(mFeatureFlags, mTelecomFlags);
mTestConnectionService.setPhoneFactoryProxy(mPhoneFactoryProxy);
mTestConnectionService.setSubscriptionManagerProxy(mSubscriptionManagerProxy);
// Set configurations statically
@@ -339,6 +353,7 @@
mBinderStub = (IConnectionService.Stub) mTestConnectionService.onBind(null);
mSetFlagsRule.enableFlags(Flags.FLAG_DO_NOT_OVERRIDE_PRECISE_LABEL);
mSetFlagsRule.enableFlags(Flags.FLAG_CALL_EXTRA_FOR_NON_HOLD_SUPPORTED_CARRIERS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_HANGUP_ACTIVE_CALL_BASED_ON_EMERGENCY_CALL_DOMAIN);
}
@After
@@ -1861,6 +1876,7 @@
@Test
@SmallTest
public void testSecondCallSameSubWontDisconnect() throws Exception {
+ doReturn(false).when(mTelecomFlags).enableCallSequencing();
// Previous test gets us into a good enough state
testIncomingDoesntRequestDisconnect();
@@ -2277,6 +2293,35 @@
}
/**
+ * For DSDA devices, verifies that calls on other subs are disconnected based on the passed in
+ * phone account
+ */
+ @Test
+ @SmallTest
+ public void testDisconnectCallsOnOtherSubs() throws Exception {
+ setupForCallTest();
+ when(mTelephonyManagerProxy.isConcurrentCallsPossible()).thenReturn(true);
+ doNothing().when(mContext).startActivityAsUser(any(), any());
+
+ mBinderStub.createConnection(PHONE_ACCOUNT_HANDLE_1, "TC@1",
+ new ConnectionRequest(PHONE_ACCOUNT_HANDLE_1, Uri.parse("tel:16505551212"),
+ new Bundle()),
+ true, false, null);
+ waitForHandlerAction(mTestConnectionService.getHandler(), TIMEOUT_MS);
+ assertEquals(1, mTestConnectionService.getAllConnections().size());
+
+ TelephonyConnection cn = (TelephonyConnection)
+ mTestConnectionService.getAllConnections().toArray()[0];
+ cn.setActive();
+
+ List<Conferenceable> conferenceables = mTestConnectionService
+ .disconnectAllConferenceablesOnOtherSubs(PHONE_ACCOUNT_HANDLE_2);
+ assertFalse(conferenceables.isEmpty());
+ assertEquals(conferenceables.getFirst(), cn);
+ assertEquals(cn.getState(), android.telecom.Connection.STATE_DISCONNECTED);
+ }
+
+ /**
* Verifies that TelephonyManager is used to determine whether a connection is Emergency when
* creating an outgoing connection.
*/
@@ -3708,6 +3753,234 @@
}
@Test
+ public void testDomainSelectionAddCsEmergencyCallWhenImsCallActive() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_HANGUP_ACTIVE_CALL_BASED_ON_EMERGENCY_CALL_DOMAIN);
+
+ setupForCallTest();
+ doReturn(1).when(mPhone0).getSubId();
+ doReturn(1).when(mImsPhone).getSubId();
+ ImsPhoneCall imsPhoneCall = Mockito.mock(ImsPhoneCall.class);
+ ImsPhoneConnection imsPhoneConnection = Mockito.mock(ImsPhoneConnection.class);
+ when(imsPhoneCall.getPhone()).thenReturn(mImsPhone);
+ when(imsPhoneConnection.getCall()).thenReturn(imsPhoneCall);
+ when(imsPhoneConnection.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
+
+ // PROPERTY_IS_EXTERNAL_CALL: to avoid extra processing that is not related to this test.
+ SimpleTelephonyConnection tc1 = createTestConnection(PHONE_ACCOUNT_HANDLE_1,
+ android.telecom.Connection.PROPERTY_IS_EXTERNAL_CALL, false);
+ // IMS connection is set.
+ tc1.setOriginalConnection(imsPhoneConnection);
+ mTestConnectionService.addExistingConnection(PHONE_ACCOUNT_HANDLE_1, tc1);
+
+ assertEquals(1, mTestConnectionService.getAllConnections().size());
+ TelephonyConnection connection1 = (TelephonyConnection)
+ mTestConnectionService.getAllConnections().toArray()[0];
+ assertEquals(tc1, connection1);
+
+ // Add a CS emergency call.
+ String telecomCallId2 = "TC2";
+ int selectedDomain = DOMAIN_CS;
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+ getTestContext().getCarrierConfig(0 /*subId*/).putBoolean(
+ CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, telecomCallId2));
+
+ // Hang up the active IMS call due to CS emergency call.
+ ArgumentCaptor<Connection.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(Connection.Listener.class);
+ verify(imsPhoneConnection).addListener(listenerCaptor.capture());
+ assertTrue(tc1.wasDisconnected);
+
+ // Call disconnection completed.
+ Connection.Listener listener = listenerCaptor.getValue();
+ assertNotNull(listener);
+ listener.onDisconnect(0);
+
+ // Continue to proceed the outgoing emergency call after active call is disconnected.
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender, times(2))
+ .onEmergencyCallStarted(any(), anyBoolean());
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ verify(mPhone0).dial(anyString(), any(), any());
+
+ android.telecom.Connection tc = connectionCaptor.getValue();
+ assertNotNull(tc);
+ assertEquals(telecomCallId2, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+ }
+
+ @Test
+ public void testDomainSelectionAddImsEmergencyCallWhenCsCallActive() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_HANGUP_ACTIVE_CALL_BASED_ON_EMERGENCY_CALL_DOMAIN);
+
+ setupForCallTest();
+
+ // PROPERTY_IS_EXTERNAL_CALL: to avoid extra processing that is not related to this test.
+ SimpleTelephonyConnection tc1 = createTestConnection(PHONE_ACCOUNT_HANDLE_1,
+ android.telecom.Connection.PROPERTY_IS_EXTERNAL_CALL, false);
+ // CS connection is set.
+ tc1.setOriginalConnection(mInternalConnection);
+ mTestConnectionService.addExistingConnection(PHONE_ACCOUNT_HANDLE_1, tc1);
+
+ assertEquals(1, mTestConnectionService.getAllConnections().size());
+ TelephonyConnection connection1 = (TelephonyConnection)
+ mTestConnectionService.getAllConnections().toArray()[0];
+ assertEquals(tc1, connection1);
+
+ // Add an IMS emergency call.
+ String telecomCallId2 = "TC2";
+ int selectedDomain = DOMAIN_PS;
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+ getTestContext().getCarrierConfig(0 /*subId*/).putBoolean(
+ CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, telecomCallId2));
+
+ // Hang up the active CS call due to IMS emergency call.
+ ArgumentCaptor<Connection.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(Connection.Listener.class);
+ verify(mInternalConnection).addListener(listenerCaptor.capture());
+ assertTrue(tc1.wasDisconnected);
+
+ // Call disconnection completed.
+ Connection.Listener listener = listenerCaptor.getValue();
+ assertNotNull(listener);
+ listener.onDisconnect(0);
+
+ // Continue to proceed the outgoing emergency call after active call is disconnected.
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender, times(2))
+ .onEmergencyCallStarted(any(), anyBoolean());
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ verify(mPhone0).dial(anyString(), any(), any());
+
+ android.telecom.Connection tc = connectionCaptor.getValue();
+ assertNotNull(tc);
+ assertEquals(telecomCallId2, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+ }
+
+ @Test
+ @SmallTest
+ public void testDomainSelectionMaybeDisconnectCallsOnOtherDomainWhenNoActiveCalls() {
+ SimpleTelephonyConnection ec = createTestConnection(PHONE_ACCOUNT_HANDLE_1, 0, true);
+ Consumer<Boolean> consumer = (result) -> {
+ if (!result) {
+ fail("Unexpected result=" + result);
+ }
+ };
+ CompletableFuture<Void> unused =
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherDomain(mPhone0,
+ ec, DOMAIN_PS, Collections.emptyList(), Collections.emptyList(), consumer);
+
+ assertTrue(unused.isDone());
+ }
+
+ @Test
+ @SmallTest
+ public void testDomainSelectionMaybeDisconnectCallsOnOtherDomainWhenConferenceOnly() {
+ setupForCallTest();
+ ArrayList<android.telecom.Conference> conferences = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(PHONE_ACCOUNT_HANDLE_1, 0, false);
+ SimpleConference conference = createTestConference(PHONE_ACCOUNT_HANDLE_1, 0);
+ tc1.setOriginalConnection(mInternalConnection);
+ conference.addConnection(tc1);
+ conferences.add(conference);
+
+ SimpleTelephonyConnection ec = createTestConnection(PHONE_ACCOUNT_HANDLE_1, 0, true);
+ Consumer<Boolean> consumer = (result) -> {
+ if (!result) {
+ fail("Unexpected result=" + result);
+ }
+ };
+ CompletableFuture<Void> unused =
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherDomain(
+ mPhone0, ec, DOMAIN_PS, Collections.emptyList(), conferences, consumer);
+
+ assertTrue(unused.isDone());
+ assertTrue(conference.wasDisconnected);
+ }
+
+ @Test
+ @SmallTest
+ public void testDomainSelectionMaybeDisconnectCallsOnOtherDomainWhenActiveCall() {
+ setupForCallTest();
+ ArrayList<android.telecom.Connection> connections = new ArrayList<>();
+ ArrayList<android.telecom.Conference> conferences = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(PHONE_ACCOUNT_HANDLE_1, 0, false);
+ SimpleConference conference = createTestConference(PHONE_ACCOUNT_HANDLE_1, 0);
+ tc1.setOriginalConnection(mInternalConnection);
+ connections.add(tc1);
+ conference.addConnection(tc1);
+ conferences.add(conference);
+
+ SimpleTelephonyConnection ec = createTestConnection(PHONE_ACCOUNT_HANDLE_1, 0, true);
+ Consumer<Boolean> consumer = (result) -> {
+ if (!result) {
+ fail("Unexpected result=" + result);
+ }
+ };
+ CompletableFuture<Void> unused =
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherDomain(
+ mPhone0, ec, DOMAIN_PS, connections, conferences, consumer);
+
+ assertFalse(unused.isDone());
+ assertTrue(tc1.wasDisconnected);
+ assertTrue(conference.wasDisconnected);
+
+ ArgumentCaptor<Connection.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(Connection.Listener.class);
+ verify(mInternalConnection).addListener(listenerCaptor.capture());
+
+ // Call disconnection completed.
+ Connection.Listener listener = listenerCaptor.getValue();
+ assertNotNull(listener);
+ listener.onDisconnect(0);
+
+ assertTrue(unused.isDone());
+ }
+
+ @Test
+ @SmallTest
+ public void testDomainSelectionMaybeDisconnectCallsOnOtherDomainWhenExceptionOccurs() {
+ setupForCallTest();
+ ArrayList<android.telecom.Connection> connections = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(PHONE_ACCOUNT_HANDLE_1, 0, false);
+ tc1.setOriginalConnection(mInternalConnection);
+ connections.add(tc1);
+ doThrow(new NullPointerException("Intended: Connection is null"))
+ .when(mInternalConnection).addListener(any());
+
+ SimpleTelephonyConnection ec = createTestConnection(PHONE_ACCOUNT_HANDLE_1, 0, true);
+ Consumer<Boolean> consumer = (result) -> {
+ if (result) {
+ fail("Unexpected result=" + result);
+ }
+ };
+ CompletableFuture<Void> unused =
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherDomain(
+ mPhone0, ec, DOMAIN_PS, connections, Collections.emptyList(), consumer);
+
+ assertTrue(unused.isDone());
+ assertFalse(tc1.wasDisconnected);
+ }
+
+ @Test
public void testDomainSelectionWithMmiCode() {
//UT domain selection should not be handled by new domain selector.
doNothing().when(mContext).startActivityAsUser(any(), any());