[automerger skipped] Revert "Unbind CS if connection is not created within 15 seconds." am: ccf524181b -s ours am: fb8e325fda -s ours am: 06a967c99e -s ours am: 00e8f94d1d -s ours
am skip reason: Merged-In I30caed1481dff5af2223a8ff589846597cee8229 with SHA-1 59052739d8 is already in history. Merged-In was found from reverted change.
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telecomm/+/28728671
Change-Id: Ie6dc7a980a0b63a1b3db795c38a0d1bca76c08b4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 60016fd..d049985 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -353,6 +353,17 @@
/** The state of the call. */
private int mState;
+ /**
+ * Determines whether the {@link ConnectionService} has responded to the initial request to
+ * create the connection.
+ *
+ * {@code false} indicates the {@link Call} has been added to Telecom, but the
+ * {@link Connection} has not yet been returned by the associated {@link ConnectionService}.
+ * {@code true} indicates the {@link Call} has an associated {@link Connection} reported by the
+ * {@link ConnectionService}.
+ */
+ private boolean mIsCreateConnectionComplete = false;
+
/** The handle with which to establish this call. */
private Uri mHandle;
@@ -1041,6 +1052,19 @@
return mConnectionService;
}
+ /**
+ * @return {@code true} if the connection has been created by the underlying
+ * {@link ConnectionService}, {@code false} otherwise.
+ */
+ public boolean isCreateConnectionComplete() {
+ return mIsCreateConnectionComplete;
+ }
+
+ @VisibleForTesting
+ public void setIsCreateConnectionComplete(boolean isCreateConnectionComplete) {
+ mIsCreateConnectionComplete = isCreateConnectionComplete;
+ }
+
@VisibleForTesting
public int getState() {
return mState;
@@ -2192,6 +2216,7 @@
CallIdMapper idMapper,
ParcelableConference conference) {
Log.v(this, "handleCreateConferenceSuccessful %s", conference);
+ mIsCreateConnectionComplete = true;
setTargetPhoneAccount(conference.getPhoneAccount());
setHandle(conference.getHandle(), conference.getHandlePresentation());
@@ -2225,6 +2250,7 @@
CallIdMapper idMapper,
ParcelableConnection connection) {
Log.v(this, "handleCreateConnectionSuccessful %s", connection);
+ mIsCreateConnectionComplete = true;
setTargetPhoneAccount(connection.getPhoneAccount());
setHandle(connection.getHandle(), connection.getHandlePresentation());
setCallerDisplayName(
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index f2fa7a5..f23a101 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -39,6 +39,7 @@
import android.telecom.DisconnectCause;
import android.telecom.GatewayInfo;
import android.telecom.Log;
+import android.telecom.Logging.Runnable;
import android.telecom.Logging.Session;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
@@ -63,6 +64,11 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
import java.util.Objects;
/**
@@ -77,6 +83,11 @@
private static final String TELECOM_ABBREVIATION = "cast";
+ private static final long SERVICE_BINDING_TIMEOUT = 15000L;
+ private ScheduledExecutorService mScheduledExecutor =
+ Executors.newSingleThreadScheduledExecutor();
+ // Pre-allocate space for 2 calls; realistically thats all we should ever need (tm)
+ private final Map<Call, ScheduledFuture<?>> mScheduledFutureMap = new ConcurrentHashMap<>(2);
private final class Adapter extends IConnectionServiceAdapter.Stub {
@Override
@@ -89,6 +100,12 @@
try {
synchronized (mLock) {
logIncoming("handleCreateConnectionComplete %s", callId);
+ Call call = mCallIdMapper.getCall(callId);
+ if (mScheduledFutureMap.containsKey(call)) {
+ ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+ existingTimeout.cancel(false /* cancelIfRunning */);
+ mScheduledFutureMap.remove(call);
+ }
// Check status hints image for cross user access
if (connection.getStatusHints() != null) {
Icon icon = connection.getStatusHints().getIcon();
@@ -133,6 +150,12 @@
conference.getStatusHints().setIcon(StatusHints.
validateAccountIconUserBoundary(icon, callingUserHandle));
}
+ Call call = mCallIdMapper.getCall(callId);
+ if (mScheduledFutureMap.containsKey(call)) {
+ ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+ existingTimeout.cancel(false /* cancelIfRunning */);
+ mScheduledFutureMap.remove(call);
+ }
ConnectionServiceWrapper.this
.handleCreateConferenceComplete(callId, request, conference);
@@ -1260,7 +1283,8 @@
* @param context The context.
* @param userHandle The {@link UserHandle} to use when binding.
*/
- ConnectionServiceWrapper(
+ @VisibleForTesting
+ public ConnectionServiceWrapper(
ComponentName componentName,
ConnectionServiceRepository connectionServiceRepository,
PhoneAccountRegistrar phoneAccountRegistrar,
@@ -1357,6 +1381,26 @@
.setIsAdhocConferenceCall(call.isAdhocConferenceCall())
.build();
+ Runnable r = new Runnable("CSW.cC", mLock) {
+ @Override
+ public void loggedRun() {
+ if (!call.isCreateConnectionComplete()) {
+ Log.e(this, new Exception(),
+ "Conference %s creation timeout",
+ getComponentName());
+ Log.addEvent(call, LogUtils.Events.CREATE_CONFERENCE_TIMEOUT,
+ Log.piiHandle(call.getHandle()) + " via:" +
+ getComponentName().getPackageName());
+ response.handleCreateConferenceFailure(
+ new DisconnectCause(DisconnectCause.ERROR));
+ }
+ }
+ };
+ // Post cleanup to the executor service and cache the future, so we can cancel it if
+ // needed.
+ ScheduledFuture<?> future = mScheduledExecutor.schedule(r.getRunnableToCancel(),
+ SERVICE_BINDING_TIMEOUT, TimeUnit.MILLISECONDS);
+ mScheduledFutureMap.put(call, future);
try {
mServiceInterface.createConference(
call.getConnectionManagerPhoneAccount(),
@@ -1459,6 +1503,26 @@
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
.build();
+ Runnable r = new Runnable("CSW.cC", mLock) {
+ @Override
+ public void loggedRun() {
+ if (!call.isCreateConnectionComplete()) {
+ Log.e(this, new Exception(),
+ "Connection %s creation timeout",
+ getComponentName());
+ Log.addEvent(call, LogUtils.Events.CREATE_CONNECTION_TIMEOUT,
+ Log.piiHandle(call.getHandle()) + " via:" +
+ getComponentName().getPackageName());
+ response.handleCreateConnectionFailure(
+ new DisconnectCause(DisconnectCause.ERROR));
+ }
+ }
+ };
+ // Post cleanup to the executor service and cache the future, so we can cancel it if
+ // needed.
+ ScheduledFuture<?> future = mScheduledExecutor.schedule(r.getRunnableToCancel(),
+ SERVICE_BINDING_TIMEOUT, TimeUnit.MILLISECONDS);
+ mScheduledFutureMap.put(call, future);
try {
mServiceInterface.createConnection(
call.getConnectionManagerPhoneAccount(),
@@ -1868,7 +1932,8 @@
}
}
- void addCall(Call call) {
+ @VisibleForTesting
+ public void addCall(Call call) {
if (mCallIdMapper.getCallId(call) == null) {
mCallIdMapper.addCall(call);
}
@@ -2335,4 +2400,9 @@
sb.append("]");
return sb.toString();
}
+
+ @VisibleForTesting
+ public void setScheduledExecutorService(ScheduledExecutorService service) {
+ mScheduledExecutor = service;
+ }
}
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 12e780f..aafc383 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -131,8 +131,10 @@
public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
public static final String START_CONNECTION = "START_CONNECTION";
public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
+ public static final String CREATE_CONNECTION_TIMEOUT = "CREATE_CONNECTION_TIMEOUT";
public static final String START_CONFERENCE = "START_CONFERENCE";
public static final String CREATE_CONFERENCE_FAILED = "CREATE_CONFERENCE_FAILED";
+ public static final String CREATE_CONFERENCE_TIMEOUT = "CREATE_CONFERENCE_TIMEOUT";
public static final String BIND_CS = "BIND_CS";
public static final String CS_BOUND = "CS_BOUND";
public static final String CONFERENCE_WITH = "CONF_WITH";
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 80c1afc..7b24a09 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -32,7 +32,6 @@
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
import android.os.AsyncTask;
import android.os.PersistableBundle;
@@ -158,14 +157,9 @@
};
public static final String FILE_NAME = "phone-account-registrar-state.xml";
- public static final String ICON_ERROR_MSG =
- "Icon cannot be written to memory. Try compressing or downsizing";
@VisibleForTesting
public static final int EXPECTED_STATE_VERSION = 9;
public static final int MAX_PHONE_ACCOUNT_REGISTRATIONS = 10;
- public static final int MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT = 100;
- public static final int MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT = 256;
- public static final int MAX_SCHEMES_PER_ACCOUNT = 10;
/** Keep in sync with the same in SipSettings.java */
private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
@@ -815,15 +809,6 @@
return getPhoneAccountHandles(0, null, packageName, false, userHandle);
}
-
- /**
- * includes disabled, includes crossUserAccess
- */
- public List<PhoneAccountHandle> getAllPhoneAccountHandlesForPackage(UserHandle userHandle,
- String packageName) {
- return getPhoneAccountHandles(0, null, packageName, true /* includeDisabled */, userHandle);
- }
-
/**
* Retrieves a list of all {@link PhoneAccount#CAPABILITY_SELF_MANAGED} phone accounts
* registered by a specified package.
@@ -862,11 +847,8 @@
* Performs checks before calling addOrReplacePhoneAccount(PhoneAccount)
*
* @param account The {@code PhoneAccount} to add or replace.
- * @throws SecurityException if package does not have BIND_TELECOM_CONNECTION_SERVICE
- * permission
+ * @throws SecurityException if package does not have BIND_TELECOM_CONNECTION_SERVICE permission
* @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached
- * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT is reached
- * @throws IllegalArgumentException if writing the Icon to memory will cause an Exception
*/
public void registerPhoneAccount(PhoneAccount account) {
// Enforce the requirement that a connection service for a phone account has the correct
@@ -878,157 +860,21 @@
throw new SecurityException("PhoneAccount connection service requires "
+ "BIND_TELECOM_CONNECTION_SERVICE permission.");
}
- enforceCharacterLimit(account);
- enforceIconSizeLimit(account);
- enforceMaxPhoneAccountLimit(account);
- addOrReplacePhoneAccount(account);
- }
-
- /**
- * Enforce an upper bound on the number of PhoneAccount's a package can register.
- * Most apps should only require 1-2. * Include disabled accounts.
- *
- * @param account to enforce check on
- * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached
- */
- private void enforceMaxPhoneAccountLimit(@NonNull PhoneAccount account) {
- final PhoneAccountHandle accountHandle = account.getAccountHandle();
- final UserHandle user = accountHandle.getUserHandle();
- final ComponentName componentName = accountHandle.getComponentName();
-
- if (getPhoneAccountHandles(0, null, componentName.getPackageName(),
- true /* includeDisabled */, user).size()
+ //Enforce an upper bound on the number of PhoneAccount's a package can register.
+ // Most apps should only require 1-2.
+ if (getPhoneAccountsForPackage(
+ account.getAccountHandle().getComponentName().getPackageName(),
+ account.getAccountHandle().getUserHandle()).size()
>= MAX_PHONE_ACCOUNT_REGISTRATIONS) {
- EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
- "enforceMaxPhoneAccountLimit");
+ Log.w(this, "Phone account %s reached max registration limit for package",
+ account.getAccountHandle());
throw new IllegalArgumentException(
"Error, cannot register phone account " + account.getAccountHandle()
+ " because the limit, " + MAX_PHONE_ACCOUNT_REGISTRATIONS
+ ", has been reached");
}
- }
- /**
- * determine if there will be an issue writing the icon to memory
- *
- * @param account to enforce check on
- * @throws IllegalArgumentException if writing the Icon to memory will cause an Exception
- */
- @VisibleForTesting
- public void enforceIconSizeLimit(PhoneAccount account) {
- if (account.getIcon() == null) {
- return;
- }
- String text = "";
- // convert the icon into a Base64 String
- try {
- text = XmlSerialization.writeIconToBase64String(account.getIcon());
- } catch (IOException e) {
- EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
- "enforceIconSizeLimit");
- throw new IllegalArgumentException(ICON_ERROR_MSG);
- }
- }
-
- /**
- * All {@link PhoneAccount} and{@link PhoneAccountHandle} String and Char-Sequence fields
- * should be restricted to character limit of MAX_PHONE_ACCOUNT_CHAR_LIMIT to prevent exceptions
- * when writing large character streams to XML-Serializer.
- *
- * @param account to enforce character limit checks on
- * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT reached
- */
- public void enforceCharacterLimit(PhoneAccount account) {
- if (account == null) {
- return;
- }
- PhoneAccountHandle handle = account.getAccountHandle();
-
- String[] fields =
- {"Package Name", "Class Name", "PhoneAccountHandle Id", "Label", "ShortDescription",
- "GroupId", "Address", "SubscriptionAddress"};
-
- CharSequence[] args = {handle.getComponentName().getPackageName(),
- handle.getComponentName().getClassName(), handle.getId(), account.getLabel(),
- account.getShortDescription(), account.getGroupId(),
- (account.getAddress() != null ? account.getAddress().toString() : ""),
- (account.getSubscriptionAddress() != null ?
- account.getSubscriptionAddress().toString() : "")};
-
- for (int i = 0; i < fields.length; i++) {
- if (args[i] != null && args[i].length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
- EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
- "enforceCharacterLimit");
- throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle ["
- + fields[i] + "] field has an invalid character count. PhoneAccount and "
- + "PhoneAccountHandle String and Char-Sequence fields are limited to "
- + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
- }
- }
-
- // Enforce limits on the URI Schemes provided
- enforceLimitsOnSchemes(account);
-
- // Enforce limit on the PhoneAccount#mExtras
- Bundle extras = account.getExtras();
- if (extras != null) {
- if (extras.keySet().size() > MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT) {
- EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
- "enforceCharacterLimit");
- throw new IllegalArgumentException("The PhoneAccount#mExtras is limited to " +
- MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT + " (key,value) pairs.");
- }
-
- for (String key : extras.keySet()) {
- Object value = extras.get(key);
-
- if ((key != null && key.length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) ||
- (value instanceof String &&
- ((String) value).length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT)) {
- EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
- "enforceCharacterLimit");
- throw new IllegalArgumentException("The PhoneAccount#mExtras contains a String"
- + " key or value that has an invalid character count. PhoneAccount and "
- + "PhoneAccountHandle String and Char-Sequence fields are limited to "
- + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
- }
- }
- }
- }
-
- /**
- * Enforce a character limit on all PA and PAH string or char-sequence fields.
- *
- * @param account to enforce check on
- * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT reached
- */
- @VisibleForTesting
- public void enforceLimitsOnSchemes(@NonNull PhoneAccount account) {
- List<String> schemes = account.getSupportedUriSchemes();
-
- if (schemes == null) {
- return;
- }
-
- if (schemes.size() > MAX_SCHEMES_PER_ACCOUNT) {
- EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
- "enforceLimitsOnSchemes");
- throw new IllegalArgumentException(
- "Error, cannot register phone account " + account.getAccountHandle()
- + " because the URI scheme limit of "
- + MAX_SCHEMES_PER_ACCOUNT + " has been reached");
- }
-
- for (String scheme : schemes) {
- if (scheme.length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
- EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
- "enforceLimitsOnSchemes");
- throw new IllegalArgumentException(
- "Error, cannot register phone account " + account.getAccountHandle()
- + " because the max scheme limit of "
- + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " has been reached");
- }
- }
+ addOrReplacePhoneAccount(account);
}
/**
@@ -1920,20 +1766,17 @@
protected void writeIconIfNonNull(String tagName, Icon value, XmlSerializer serializer)
throws IOException {
if (value != null) {
- String text = writeIconToBase64String(value);
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ value.writeToStream(stream);
+ byte[] iconByteArray = stream.toByteArray();
+ String text = Base64.encodeToString(iconByteArray, 0, iconByteArray.length, 0);
+
serializer.startTag(null, tagName);
serializer.text(text);
serializer.endTag(null, tagName);
}
}
- public static String writeIconToBase64String(Icon icon) throws IOException {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- icon.writeToStream(stream);
- byte[] iconByteArray = stream.toByteArray();
- return Base64.encodeToString(iconByteArray, 0, iconByteArray.length, 0);
- }
-
protected void writeLong(String tagName, long value, XmlSerializer serializer)
throws IOException {
serializer.startTag(null, tagName);
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 37e9d33..576ac10 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -71,9 +71,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
// TODO: Needed for move to system service: import com.android.internal.R;
@@ -349,7 +347,7 @@
try {
Log.startSession("TSI.gPAFP");
return new ParceledListSlice<>(mPhoneAccountRegistrar
- .getAllPhoneAccountHandlesForPackage(callingUserHandle, packageName));
+ .getPhoneAccountsForPackage(packageName, callingUserHandle));
} catch (Exception e) {
Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
throw e;
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 7bce5a6..f5e6f42 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -993,6 +993,7 @@
call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
assert(call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
+ call.setIsCreateConnectionComplete(true);
}
/**
@@ -1016,6 +1017,7 @@
call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
assert(!call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
+ call.setIsCreateConnectionComplete(true);
}
/**
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index e7d0a26..acc2dd6 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -41,6 +41,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.lang.Thread.sleep;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -49,6 +50,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
@@ -67,6 +69,7 @@
import android.util.Pair;
import android.widget.Toast;
+import com.android.internal.telecom.IConnectionService;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
@@ -80,6 +83,7 @@
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CreateConnectionResponse;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallHelper;
import com.android.server.telecom.HeadsetMediaButton;
@@ -211,6 +215,7 @@
@Mock private RoleManagerAdapter mRoleManagerAdapter;
@Mock private ToastFactory mToastFactory;
@Mock private Toast mToast;
+ @Mock private IConnectionService mIConnectionService;
private CallsManager mCallsManager;
@@ -278,11 +283,19 @@
eq(SIM_2_HANDLE), any())).thenReturn(SIM_2_ACCOUNT);
when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
+ when(mIConnectionService.asBinder()).thenReturn(mock(IBinder.class));
+
+ mComponentContextFixture.addConnectionService(new ComponentName(mContext.getPackageName(),
+ mContext.getPackageName().getClass().getName()), mIConnectionService);
}
@Override
@After
public void tearDown() throws Exception {
+ mComponentContextFixture.removeConnectionService(
+ new ComponentName(mContext.getPackageName(),
+ mContext.getPackageName().getClass().getName()),
+ mock(IConnectionService.class));
super.tearDown();
}
@@ -1695,6 +1708,32 @@
assertTrue(argumentCaptor.getValue().contains("Unavailable phoneAccountHandle"));
}
+ @Test
+ public void testConnectionServiceCreateConnectionTimeout() throws Exception {
+ ConnectionServiceWrapper service = new ConnectionServiceWrapper(new ComponentName(
+ mContext.getPackageName(), mContext.getPackageName().getClass().getName()), null,
+ mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null);
+ TestScheduledExecutorService scheduledExecutorService = new TestScheduledExecutorService();
+ service.setScheduledExecutorService(scheduledExecutorService);
+ Call call = addSpyCall();
+ service.addCall(call);
+ when(call.isCreateConnectionComplete()).thenReturn(false);
+ CreateConnectionResponse response = mock(CreateConnectionResponse.class);
+
+ service.createConnection(call, response);
+ waitUntilConditionIsTrueOrTimeout(new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ return scheduledExecutorService.isRunnableScheduledAtTime(15000L);
+ }
+ }, 5000L, "Expected job failed to schedule");
+ }
+
private Call addSpyCall() {
return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
}
@@ -1787,4 +1826,19 @@
when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
}
+
+ private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+ String description) throws InterruptedException {
+ final long start = System.currentTimeMillis();
+ while (!condition.expected().equals(condition.actual())
+ && System.currentTimeMillis() - start < timeout) {
+ sleep(50);
+ }
+ assertEquals(description, condition.expected(), condition.actual());
+ }
+
+ protected interface Condition {
+ Object expected();
+ Object actual();
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 639ac22..c728ac8 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -690,6 +690,14 @@
mServiceInfoByComponentName.put(componentName, serviceInfo);
}
+ public void removeConnectionService(
+ ComponentName componentName,
+ IConnectionService service)
+ throws Exception {
+ removeService(ConnectionService.SERVICE_INTERFACE, componentName, service);
+ mServiceInfoByComponentName.remove(componentName);
+ }
+
public void addInCallService(
ComponentName componentName,
IInCallService service,
@@ -775,6 +783,12 @@
mComponentNameByService.put(service, name);
}
+ private void removeService(String action, ComponentName name, IInterface service) {
+ mComponentNamesByAction.remove(action, name);
+ mServiceByComponentName.remove(name);
+ mComponentNameByService.remove(service);
+ }
+
private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
List<ResolveInfo> result = new ArrayList<>();
for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index fb92ae6..ffa08e2 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -23,17 +23,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -90,8 +83,6 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -109,7 +100,6 @@
private final String PACKAGE_2 = "PACKAGE_2";
private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
- private static final String TEST_ID = "123";
private PhoneAccountRegistrar mRegistrar;
@Mock private SubscriptionManager mSubscriptionManager;
@Mock private TelecomManager mTelecomManager;
@@ -1264,109 +1254,6 @@
defaultPhoneAccountHandle.phoneAccountHandle.getId());
}
- /**
- * Ensure an IllegalArgumentException is thrown when adding more than 10 schemes for a single
- * account
- */
- @Test
- public void testLimitOnSchemeCount() {
- PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
- PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, TEST_LABEL);
- for (int i = 0; i < PhoneAccountRegistrar.MAX_PHONE_ACCOUNT_REGISTRATIONS + 1; i++) {
- builder.addSupportedUriScheme(Integer.toString(i));
- }
- try {
- mRegistrar.enforceLimitsOnSchemes(builder.build());
- fail("should have hit exception in enforceLimitOnSchemes");
- } catch (IllegalArgumentException e) {
- // pass test
- }
- }
-
- /**
- * Ensure an IllegalArgumentException is thrown when adding more 256 chars for a single
- * account
- */
- @Test
- public void testLimitOnSchemeLength() {
- PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
- PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, TEST_LABEL);
- builder.addSupportedUriScheme(generateStringOfLen(257));
- try {
- mRegistrar.enforceLimitsOnSchemes(builder.build());
- fail("should have hit exception in enforceLimitOnSchemes");
- } catch (IllegalArgumentException e) {
- // pass test
- }
- }
-
- /**
- * Ensure an IllegalArgumentException is thrown when adding an address over the limit
- */
- @Test
- public void testLimitOnAddress() {
- String text = generateStringOfLen(100);
- PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
- PhoneAccount.Builder builder = new PhoneAccount.Builder(handle,TEST_LABEL)
- .setAddress(Uri.fromParts(text, text, text));
- try {
- mRegistrar.enforceCharacterLimit(builder.build());
- fail("failed to throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass test
- }
- finally {
- mRegistrar.unregisterPhoneAccount(handle);
- }
- }
-
- /**
- * Ensure an IllegalArgumentException is thrown when an Icon that throws an IOException is given
- */
- @Test
- public void testLimitOnIcon() throws Exception {
- Icon mockIcon = mock(Icon.class);
- // GIVEN
- PhoneAccount.Builder builder = new PhoneAccount.Builder(
- makeQuickAccountHandle(TEST_ID), TEST_LABEL).setIcon(mockIcon);
- try {
- // WHEN
- Mockito.doThrow(new IOException())
- .when(mockIcon).writeToStream(any(OutputStream.class));
- //THEN
- mRegistrar.enforceIconSizeLimit(builder.build());
- fail("failed to throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass test
- assertTrue(e.getMessage().contains(PhoneAccountRegistrar.ICON_ERROR_MSG));
- }
- }
-
- /**
- * Ensure an IllegalArgumentException is thrown when providing a SubscriptionAddress that
- * exceeds the PhoneAccountRegistrar limit.
- */
- @Test
- public void testLimitOnSubscriptionAddress() throws Exception {
- String text = generateStringOfLen(100);
- PhoneAccount.Builder builder = new PhoneAccount.Builder(makeQuickAccountHandle(TEST_ID),
- TEST_LABEL).setSubscriptionAddress(Uri.fromParts(text, text, text));
- try {
- mRegistrar.enforceCharacterLimit(builder.build());
- fail("failed to throw IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // pass test
- }
- }
-
- private String generateStringOfLen(int len){
- StringBuilder sb = new StringBuilder();
- for(int i=0; i < len; i++){
- sb.append("a");
- }
- return sb.toString();
- }
-
private static ComponentName makeQuickConnectionServiceComponentName() {
return new ComponentName(
"com.android.server.telecom.tests",
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 572c975..2d2b4bc 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -454,7 +454,7 @@
add(SIP_PA_HANDLE_17);
}};
when(mFakePhoneAccountRegistrar
- .getAllPhoneAccountHandlesForPackage(any(UserHandle.class), anyString()))
+ .getPhoneAccountsForPackage(anyString(), any(UserHandle.class)))
.thenReturn(phoneAccountHandleList);
makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
assertEquals(phoneAccountHandleList,
diff --git a/tests/src/com/android/server/telecom/tests/TestScheduledExecutorService.java b/tests/src/com/android/server/telecom/tests/TestScheduledExecutorService.java
new file mode 100644
index 0000000..8ddf42b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/TestScheduledExecutorService.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A test implementation of a scheduled executor service.
+ */
+public class TestScheduledExecutorService implements ScheduledExecutorService {
+ private static final String TAG = "TestScheduledExecutorService";
+
+ private class CompletedFuture<T> implements Future<T>, ScheduledFuture<T> {
+
+ private final Callable<T> mTask;
+ private final long mDelayMs;
+ private Runnable mRunnable;
+
+ CompletedFuture(Callable<T> task) {
+ mTask = task;
+ mDelayMs = 0;
+ }
+
+ @SuppressWarnings("unused")
+ CompletedFuture(Callable<T> task, long delayMs) {
+ mTask = task;
+ mDelayMs = delayMs;
+ }
+
+ CompletedFuture(Runnable task, long delayMs) {
+ mRunnable = task;
+ mTask = (Callable<T>) Executors.callable(task);
+ mDelayMs = delayMs;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ cancelRunnable(mRunnable);
+ return true;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ try {
+ return mTask.call();
+ } catch (Exception e) {
+ throw new ExecutionException(e);
+ }
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ try {
+ return mTask.call();
+ } catch (Exception e) {
+ throw new ExecutionException(e);
+ }
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ if (unit == TimeUnit.MILLISECONDS) {
+ return mDelayMs;
+ } else {
+ // not implemented
+ return 0;
+ }
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ if (o == null) return 1;
+ if (o.getDelay(TimeUnit.MILLISECONDS) > mDelayMs) return -1;
+ if (o.getDelay(TimeUnit.MILLISECONDS) < mDelayMs) return 1;
+ return 0;
+ }
+ }
+
+ private long mClock = 0;
+ private Map<Long, Runnable> mScheduledRunnables = new HashMap<>();
+ private Map<Runnable, Long> mRepeatDuration = new HashMap<>();
+
+ @Override
+ public void shutdown() {
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) {
+ return false;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return new TestScheduledExecutorService.CompletedFuture<>(task);
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ task.run();
+ return new TestScheduledExecutorService.CompletedFuture<>(() -> null);
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
+ TimeUnit unit) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ // Schedule the runnable for execution at the specified time.
+ long scheduledTime = getNextExecutionTime(delay, unit);
+ mScheduledRunnables.put(scheduledTime, command);
+
+ Log.i(TAG, "schedule: runnable=" + System.identityHashCode(command) + ", time="
+ + scheduledTime);
+
+ return new TestScheduledExecutorService.CompletedFuture<Runnable>(command, delay);
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
+ TimeUnit unit) {
+ return scheduleWithFixedDelay(command, initialDelay, period, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+ long delay, TimeUnit unit) {
+ // Schedule the runnable for execution at the specified time.
+ long nextScheduledTime = getNextExecutionTime(delay, unit);
+ mScheduledRunnables.put(nextScheduledTime, command);
+ mRepeatDuration.put(command, unit.toMillis(delay));
+
+ return new TestScheduledExecutorService.CompletedFuture<Runnable>(command, delay);
+ }
+
+ private long getNextExecutionTime(long delay, TimeUnit unit) {
+ long delayMillis = unit.toMillis(delay);
+ return mClock + delayMillis;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+
+ /**
+ * Used in unit tests, used to add a delta to the "clock" so that we can fire off scheduled
+ * items and reschedule the repeats.
+ * @param duration The duration (millis) to add to the clock.
+ */
+ public void advanceTime(long duration) {
+ Map<Long, Runnable> nextRepeats = new HashMap<>();
+ List<Runnable> toRun = new ArrayList<>();
+ mClock += duration;
+ Iterator<Map.Entry<Long, Runnable>> iterator = mScheduledRunnables.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<Long, Runnable> entry = iterator.next();
+ if (mClock >= entry.getKey()) {
+ toRun.add(entry.getValue());
+
+ Runnable r = entry.getValue();
+ Log.i(TAG, "advanceTime: runningRunnable=" + System.identityHashCode(r));
+ // If this is a repeating scheduled item, schedule the repeat.
+ if (mRepeatDuration.containsKey(r)) {
+ // schedule next execution
+ nextRepeats.put(mClock + mRepeatDuration.get(r), entry.getValue());
+ }
+ iterator.remove();
+ }
+ }
+
+ // Update things at the end to avoid concurrent access.
+ mScheduledRunnables.putAll(nextRepeats);
+ toRun.forEach(r -> r.run());
+ }
+
+ /**
+ * Used from a {@link CompletedFuture} as defined above to cancel a scheduled task.
+ * @param r The runnable to cancel.
+ */
+ private void cancelRunnable(Runnable r) {
+ Optional<Map.Entry<Long, Runnable>> found = mScheduledRunnables.entrySet().stream()
+ .filter(e -> e.getValue() == r)
+ .findFirst();
+ if (found.isPresent()) {
+ mScheduledRunnables.remove(found.get().getKey());
+ }
+ mRepeatDuration.remove(r);
+ Log.i(TAG, "cancelRunnable: runnable=" + System.identityHashCode(r));
+ }
+
+ public int getNumberOfScheduledRunnables() {
+ return mScheduledRunnables.size();
+ }
+
+ public boolean isRunnableScheduledAtTime(long time) {
+ return mScheduledRunnables.containsKey(time);
+ }
+}
\ No newline at end of file