Merge "Hang up active call based on emergency call domain" into main
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index f4b2545..b51ffff 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -13283,6 +13283,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;
}
@@ -14181,6 +14182,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.
@@ -14251,6 +14276,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.
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/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/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/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index dac566e..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,7 +148,7 @@
// 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.
@@ -749,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;
@@ -1343,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) -> {
@@ -1373,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,
@@ -2683,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;
@@ -3968,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());
@@ -3981,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
@@ -4914,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
@@ -4929,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) {
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 324bcbc..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,7 @@
@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(
@@ -152,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();
@@ -213,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();
@@ -252,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);
@@ -294,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(
@@ -316,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);
@@ -480,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
@@ -503,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 88b84a7..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)
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/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 6c577a5..2605114 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -2293,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.
*/