Merge "fix toggling hold for a single transactional call" into udc-dev
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 591ce72..6e5826b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -39,6 +39,7 @@
import android.provider.CallLog;
import android.provider.ContactsContract.Contacts;
import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAttributes;
import android.telecom.CallAudioState;
import android.telecom.CallDiagnosticService;
import android.telecom.CallDiagnostics;
@@ -556,7 +557,25 @@
private boolean mIsSelfManaged = false;
private boolean mIsTransactionalCall = false;
- private int mOwnerPid = -1;
+ private CallingPackageIdentity mCallingPackageIdentity = new CallingPackageIdentity();
+
+ /**
+ * CallingPackageIdentity is responsible for storing properties about the calling package that
+ * initiated the call. For example, if MyVoipApp requests to add a call with Telecom, we can
+ * store their UID and PID when we are still bound to that package.
+ */
+ public static class CallingPackageIdentity {
+ public int mCallingPackageUid = -1;
+ public int mCallingPackagePid = -1;
+
+ public CallingPackageIdentity() {
+ }
+
+ CallingPackageIdentity(Bundle extras) {
+ mCallingPackageUid = extras.getInt(CallAttributes.CALLER_UID_KEY, -1);
+ mCallingPackagePid = extras.getInt(CallAttributes.CALLER_PID_KEY, -1);
+ }
+ }
/**
* Indicates whether this call is streaming.
@@ -1832,12 +1851,15 @@
setConnectionProperties(getConnectionProperties());
}
- public void setOwnerPid(int pid) {
- mOwnerPid = pid;
+ public void setCallingPackageIdentity(Bundle extras) {
+ mCallingPackageIdentity = new CallingPackageIdentity(extras);
+ // These extras should NOT be propagated to Dialer and should be removed.
+ extras.remove(CallAttributes.CALLER_PID_KEY);
+ extras.remove(CallAttributes.CALLER_UID_KEY);
}
- public int getOwnerPid() {
- return mOwnerPid;
+ public CallingPackageIdentity getCallingPackageIdentity() {
+ return mCallingPackageIdentity;
}
public void setTransactionServiceWrapper(TransactionalServiceWrapper service) {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 91c6079..d775350 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -1405,8 +1405,7 @@
// set properties for transactional call
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
- call.setOwnerPid(extras.getInt(CallAttributes.CALLER_PID, -1));
- extras.remove(CallAttributes.CALLER_PID);
+ call.setCallingPackageIdentity(extras);
call.setConnectionCapabilities(
extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -1717,8 +1716,7 @@
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
- call.setOwnerPid(extras.getInt(CallAttributes.CALLER_PID, -1));
- extras.remove(CallAttributes.CALLER_PID);
+ call.setCallingPackageIdentity(extras);
call.setConnectionCapabilities(
extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -3658,40 +3656,57 @@
* @param call The call.
*/
private void performRemoval(Call call) {
- mInCallController.getBindingFuture().thenRunAsync(() -> {
- call.maybeCleanupHandover();
- removeCall(call);
- Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
- if (mLocallyDisconnectingCalls.contains(call)) {
- boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
- Log.v(this, "performRemoval: isDisconnectingChildCall = "
- + isDisconnectingChildCall + "call -> %s", call);
- mLocallyDisconnectingCalls.remove(call);
- // Auto-unhold the foreground call due to a locally disconnected call, except if the
- // call which was disconnected is a member of a conference (don't want to auto
- // un-hold the conference if we remove a member of the conference).
- if (!isDisconnectingChildCall && foregroundCall != null
- && foregroundCall.getState() == CallState.ON_HOLD) {
- foregroundCall.unhold();
- }
- } else if (foregroundCall != null &&
- !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
- foregroundCall.getState() == CallState.ON_HOLD) {
+ if (mInCallController.getBindingFuture() != null) {
+ mInCallController.getBindingFuture().thenRunAsync(() -> {
+ doRemoval(call);
+ }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
+ .exceptionally((throwable) -> {
+ Log.e(TAG, throwable, "Error while executing call removal");
+ mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
+ CALL_REMOVAL_EXECUTION_ERROR_MSG);
+ return null;
+ });
+ } else {
+ doRemoval(call);
+ }
+ }
- // The new foreground call is on hold, however the carrier does not display the hold
- // button in the UI. Therefore, we need to auto unhold the held call since the user
- // has no means of unholding it themselves.
- Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
- + "support hold)");
+ /**
+ * Code to perform removal of a call. Called above from {@link #performRemoval(Call)} either
+ * async (in live code) or sync (in testing).
+ * @param call the call to remove.
+ */
+ private void doRemoval(Call call) {
+ call.maybeCleanupHandover();
+ removeCall(call);
+ Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+ if (mLocallyDisconnectingCalls.contains(call)) {
+ boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
+ Log.v(this, "performRemoval: isDisconnectingChildCall = "
+ + isDisconnectingChildCall + "call -> %s", call);
+ mLocallyDisconnectingCalls.remove(call);
+ // Auto-unhold the foreground call due to a locally disconnected call, except if the
+ // call which was disconnected is a member of a conference (don't want to auto
+ // un-hold the conference if we remove a member of the conference).
+ // Also, ensure that the call we're removing is from the same ConnectionService as
+ // the one we're removing. We don't want to auto-unhold between ConnectionService
+ // implementations, especially if one is managed and the other is a VoIP CS.
+ if (!isDisconnectingChildCall && foregroundCall != null
+ && foregroundCall.getState() == CallState.ON_HOLD
+ && areFromSameSource(foregroundCall, call)) {
foregroundCall.unhold();
}
- }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
- .exceptionally((throwable) -> {
- Log.e(TAG, throwable, "Error while executing call removal");
- mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
- CALL_REMOVAL_EXECUTION_ERROR_MSG);
- return null;
- });
+ } else if (foregroundCall != null &&
+ !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
+ foregroundCall.getState() == CallState.ON_HOLD) {
+
+ // The new foreground call is on hold, however the carrier does not display the hold
+ // button in the UI. Therefore, we need to auto unhold the held call since the user
+ // has no means of unholding it themselves.
+ Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
+ + "support hold)");
+ foregroundCall.unhold();
+ }
}
/**
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 47c4acf..99a8d3d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -47,6 +47,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -195,14 +196,15 @@
// add extras about info used for FGS delegation
Bundle extras = new Bundle();
- extras.putInt(CallAttributes.CALLER_PID, Binder.getCallingPid());
+ extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
+ extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
VoipCallTransaction transaction = null;
// create transaction based on the call direction
switch (callAttributes.getDirection()) {
case DIRECTION_OUTGOING:
transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
- mCallsManager);
+ mCallsManager, extras);
break;
case DIRECTION_INCOMING:
transaction = new IncomingCallTransaction(callId, callAttributes,
@@ -366,12 +368,13 @@
}
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null,
includeDisabledAccounts, callingUserHandle,
- hasInAppCrossUserPermission()));
+ crossUserAccess));
} catch (Exception e) {
Log.e(this, e, "getCallCapablePhoneAccounts");
mAnomalyReporter.reportAnomaly(GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID,
@@ -644,11 +647,12 @@
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(mPhoneAccountRegistrar
.getAllPhoneAccountHandles(callingUserHandle,
- hasInAppCrossUserPermission()));
+ crossUserAccess));
} catch (Exception e) {
Log.e(this, e, "getAllPhoneAccounts");
throw e;
@@ -774,6 +778,9 @@
.build();
}
+ // Validate the profile boundary of the given image URI.
+ validateAccountIconUserBoundary(account.getIcon());
+
final long token = Binder.clearCallingIdentity();
try {
Log.i(this, "registerPhoneAccount: account=%s",
@@ -952,12 +959,13 @@
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
try {
Log.i(this, "Silence Ringer requested by %s", callingPackage);
Set<UserHandle> userHandles = mCallsManager.getCallAudioManager().
silenceRingers(mContext, callingUserHandle,
- hasInAppCrossUserPermission());
+ crossUserAccess);
mCallsManager.getInCallController().silenceRinger(userHandles);
} finally {
Binder.restoreCallingIdentity(token);
@@ -3130,4 +3138,22 @@
mContext.sendBroadcast(intent);
}
}
+
+ private void validateAccountIconUserBoundary(Icon icon) {
+ // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+ // incompatible types.
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+ String encodedUser = icon.getUri().getEncodedUserInfo();
+ // If there is no encoded user, the URI is calling into the calling user space
+ if (encodedUser != null) {
+ int userId = Integer.parseInt(encodedUser);
+ if (userId != UserHandle.getUserId(Binder.getCallingUid())) {
+ // If we are transcending the profile boundary, throw an error.
+ throw new IllegalArgumentException("Attempting to register a phone account with"
+ + " an image icon belonging to another user.");
+ }
+ }
+ }
+ }
}
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 1ba06ec..c5fdd4c 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -416,7 +416,7 @@
*/
public static long getNonVoipCallTransitoryStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
+ TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 10000L);
}
/**
@@ -426,7 +426,7 @@
*/
public static long getNonVoipEmergencyCallTransitoryStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 5000L);
+ TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 10000L);
}
/**
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index efa9417..ef85fc7 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -214,7 +214,7 @@
.stopFlashNotificationSequence(context);
}
},
- Executors.newSingleThreadExecutor(),
+ Executors.newCachedThreadPool(),
new BlockedNumbersAdapter() {
@Override
public boolean shouldShowEmergencyCallNotification(Context
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
index a0955c8..98faf3d 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.voip;
+import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
+
import android.os.OutcomeReceiver;
import android.telecom.TelecomManager;
import android.telecom.CallException;
@@ -79,8 +81,8 @@
@Override
public void onTransactionTimeout(String transactionName){
- receiver.onResult(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_FAILED, transactionName + " timeout"));
+ receiver.onError(new CallException(transactionName + " timeout",
+ CODE_OPERATION_TIMED_OUT));
finishTransaction();
}
});
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
index 04af98f..84fdb5d 100644
--- a/src/com/android/server/telecom/voip/VoipCallMonitor.java
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -49,7 +49,9 @@
public class VoipCallMonitor extends CallsManagerListenerBase {
private final List<Call> mPendingCalls;
- private final Map<StatusBarNotification, Call> mNotifications;
+ // Same notification may be passed as different object in onNotificationPosted and
+ // onNotificationRemoved. Use its string as key to cache ongoing notifications.
+ private final Map<String, Call> mNotifications;
private final Map<PhoneAccountHandle, Set<Call>> mPhoneAccountHandleListMap;
private ActivityManagerInternal mActivityManagerInternal;
private final Map<PhoneAccountHandle, ServiceConnection> mServices;
@@ -58,6 +60,7 @@
private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Context mContext;
+ private List<StatusBarNotification> mPendingSBN;
public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
mContext = context;
@@ -65,6 +68,7 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mPendingCalls = new ArrayList<>();
+ mPendingSBN = new ArrayList<>();
mNotifications = new HashMap<>();
mServices = new HashMap<>();
mPhoneAccountHandleListMap = new HashMap<>();
@@ -74,24 +78,21 @@
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
synchronized (mLock) {
- if (mPendingCalls.isEmpty()) {
- return;
- }
if (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
- String packageName = sbn.getPackageName();
- UserHandle userHandle = sbn.getUser();
-
+ boolean sbnMatched = false;
for (Call call : mPendingCalls) {
- if (packageName != null &&
- packageName.equals(call.getTargetPhoneAccount()
- .getComponentName().getPackageName())
- && userHandle != null
- && userHandle.equals(call.getInitiatingUser())) {
+ if (notificationMatchedCall(sbn, call)) {
mPendingCalls.remove(call);
- mNotifications.put(sbn, call);
+ mNotifications.put(sbn.toString(), call);
+ sbnMatched = true;
break;
}
}
+ if (!sbnMatched) {
+ // notification may posted before we started to monitor the call, cache
+ // this notification and try to match it later with new added call.
+ mPendingSBN.add(sbn);
+ }
}
}
}
@@ -99,12 +100,13 @@
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
synchronized (mLock) {
+ mPendingSBN.remove(sbn);
if (mNotifications.isEmpty()) {
return;
}
- Call call = mNotifications.getOrDefault(sbn, null);
+ Call call = mNotifications.getOrDefault(sbn.toString(), null);
if (call != null) {
- mNotifications.remove(sbn, call);
+ mNotifications.remove(sbn.toString(), call);
stopFGSDelegation(call.getTargetPhoneAccount());
}
}
@@ -143,8 +145,9 @@
k -> new HashSet<>());
callList.add(call);
- mHandler.post(() -> startFGSDelegation(call.getOwnerPid(),
- phoneAccountHandle.getUserHandle().getIdentifier(), call));
+ mHandler.post(
+ () -> startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
+ call.getCallingPackageIdentity().mCallingPackageUid, call));
}
}
@@ -224,15 +227,31 @@
}
private void startMonitorNotification(Call call) {
- mPendingCalls.add(call);
- mHandler.postDelayed(() -> {
- synchronized (mLock) {
- if (mPendingCalls.contains(call)) {
- stopFGSDelegation(call.getTargetPhoneAccount());
- mPendingCalls.remove(call);
+ synchronized (mLock) {
+ boolean sbnMatched = false;
+ for (StatusBarNotification sbn : mPendingSBN) {
+ if (notificationMatchedCall(sbn, call)) {
+ mPendingSBN.remove(sbn);
+ mNotifications.put(sbn.toString(), call);
+ sbnMatched = true;
+ break;
}
}
- }, 5000L);
+ if (!sbnMatched) {
+ // Only continue to
+ mPendingCalls.add(call);
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ if (mPendingCalls.contains(call)) {
+ Log.i(this, "Notification for voip-call %s haven't "
+ + "posted in time, stop delegation.", call.getId());
+ stopFGSDelegation(call.getTargetPhoneAccount());
+ mPendingCalls.remove(call);
+ }
+ }
+ }, 5000L);
+ }
+ }
}
private void stopMonitorNotification(Call call) {
@@ -248,4 +267,16 @@
public void setNotificationListenerService(NotificationListenerService listener) {
mNotificationListener = listener;
}
+
+ private boolean notificationMatchedCall(StatusBarNotification sbn, Call call) {
+ String packageName = sbn.getPackageName();
+ UserHandle userHandle = sbn.getUser();
+ PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
+
+ return packageName != null &&
+ packageName.equals(call.getTargetPhoneAccount()
+ .getComponentName().getPackageName())
+ && userHandle != null
+ && userHandle.equals(accountHandle.getUserHandle());
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 6b817d8..997e7dd 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -26,16 +26,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.ComponentName;
import android.content.Intent;
@@ -43,7 +38,7 @@
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
+import android.telecom.CallAttributes;
import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -56,12 +51,10 @@
import android.telecom.VideoProfile;
import android.telephony.CallQuality;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
import android.widget.Toast;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.internal.telecom.IVideoProvider;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallIdMapper;
import com.android.server.telecom.CallState;
@@ -69,8 +62,6 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.InCallController;
-import com.android.server.telecom.InCallController.InCallServiceInfo;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
@@ -640,8 +631,40 @@
verify(listener).onFailedUnknownCall(unknownCall);
}
+ /**
+ * ensure a Call object does not throw an NPE when the CallingPackageIdentity is not set and
+ * the correct values are returned when set
+ */
@Test
@SmallTest
+ public void testCallingPackageIdentity() {
+ final int packageUid = 123;
+ final int packagePid = 1;
+
+ Call call = createCall("1");
+
+ // assert default values for a Calls CallingPackageIdentity are -1 unless set via the setter
+ assertEquals(-1, call.getCallingPackageIdentity().mCallingPackageUid);
+ assertEquals(-1, call.getCallingPackageIdentity().mCallingPackagePid);
+
+ // set the Call objects CallingPackageIdentity via the setter and a bundle
+ Bundle extras = new Bundle();
+ extras.putInt(CallAttributes.CALLER_UID_KEY, packageUid);
+ extras.putInt(CallAttributes.CALLER_PID_KEY, packagePid);
+ // assert that the setter removed the extras
+ assertEquals(packageUid, extras.getInt(CallAttributes.CALLER_UID_KEY));
+ assertEquals(packagePid, extras.getInt(CallAttributes.CALLER_PID_KEY));
+ call.setCallingPackageIdentity(extras);
+ // assert that the setter removed the extras
+ assertEquals(0, extras.getInt(CallAttributes.CALLER_UID_KEY));
+ assertEquals(0, extras.getInt(CallAttributes.CALLER_PID_KEY));
+ // assert the properties are fetched correctly
+ assertEquals(packageUid, call.getCallingPackageIdentity().mCallingPackageUid);
+ assertEquals(packagePid, call.getCallingPackageIdentity().mCallingPackagePid);
+ }
+
+ @Test
+ @SmallTest
public void testOnConnectionEventNotifiesListener() {
Call.Listener listener = mock(Call.Listener.class);
Call call = createCall("1");
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 7ea3568..129bba2 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -688,6 +688,54 @@
verify(heldCall).unhold(any());
}
+ /**
+ * Ensures we don't auto-unhold a call from a different app when we locally disconnect a call.
+ */
+ @SmallTest
+ @Test
+ public void testDontUnholdCallsBetweenConnectionServices() {
+ // GIVEN a CallsManager with ongoing call
+ Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+ when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+ // and a held call which has different ConnectionService
+ Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
+
+ // Disconnect and cleanup the active ongoing call.
+ mCallsManager.disconnectCall(ongoingCall);
+ mCallsManager.markCallAsRemoved(ongoingCall);
+
+ // Should not unhold the held call since its in another app.
+ verify(heldCall, never()).unhold();
+ }
+
+ /**
+ * Ensures we do auto-unhold a call from the same app when we locally disconnect a call.
+ */
+ @SmallTest
+ @Test
+ public void testUnholdCallWhenDisconnectingInSameApp() {
+ // GIVEN a CallsManager with ongoing call
+ Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+ when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+ // and a held call which has same ConnectionService
+ Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
+
+ // Disconnect and cleanup the active ongoing call.
+ mCallsManager.disconnectCall(ongoingCall);
+ mCallsManager.markCallAsRemoved(ongoingCall);
+
+ // Should auto-unhold the held call since its in the same app.
+ verify(heldCall).unhold();
+ }
+
@SmallTest
@Test
public void testUnholdCallWhenOngoingEmergCallCanNotBeHeldAndFromDifferentConnectionService() {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index a98da83..7b5afe6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -33,6 +33,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -950,6 +951,26 @@
@SmallTest
@Test
+ public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
+ String packageNameToUse = "com.android.officialpackage";
+ PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+ packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+ Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+ PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+ // This should fail; security exception will be thrown.
+ registerPhoneAccountTestHelper(phoneAccount, false);
+
+ icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+ phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+ // This should succeed.
+ registerPhoneAccountTestHelper(phoneAccount, true);
+ }
+
+ @SmallTest
+ @Test
public void testUnregisterPhoneAccount() throws RemoteException {
String packageNameToUse = "com.android.officialpackage";
PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
index 7b6bd3e..346b3d8 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -212,6 +212,7 @@
when(call.isTransactionalCall()).thenReturn(true);
when(call.getExtras()).thenReturn(new Bundle());
when(call.getId()).thenReturn(id);
+ when(call.getCallingPackageIdentity()).thenReturn( new Call.CallingPackageIdentity() );
return call;
}
}
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
index 62b8bc4..e2c7b7b 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -211,7 +211,7 @@
subTransactions.add(t3);
CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
- new OutcomeReceiver<VoipCallTransactionResult, CallException>() {
+ new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
@@ -234,12 +234,20 @@
throws ExecutionException, InterruptedException, TimeoutException {
VoipCallTransaction t = new TestVoipCallTransaction("t", 10000L,
TestVoipCallTransaction.SUCCESS);
- CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+ CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
- resultFuture::complete;
- mTransactionManager.addTransaction(t, outcomeReceiver);
- VoipCallTransactionResult result = resultFuture.get(7000L, TimeUnit.MILLISECONDS);
- assertEquals(VoipCallTransactionResult.RESULT_FAILED, result.getResult());
- assertTrue(result.getMessage().contains("timeout"));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+
+ }
+
+ @Override
+ public void onError(CallException e) {
+ exceptionFuture.complete(e.getMessage());
+ }
+ }; mTransactionManager.addTransaction(t, outcomeReceiver);
+ String message = exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
+ assertTrue(message.contains("timeout"));
}
}