1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.telephony.ims.cts; 18 19 import static junit.framework.Assert.assertTrue; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 26 import android.telephony.ims.DelegateRegistrationState; 27 import android.telephony.ims.DelegateRequest; 28 import android.telephony.ims.FeatureTagState; 29 import android.telephony.ims.ImsException; 30 import android.telephony.ims.SipDelegateConfiguration; 31 import android.telephony.ims.SipDelegateConnection; 32 import android.telephony.ims.SipDelegateManager; 33 import android.telephony.ims.SipMessage; 34 import android.telephony.ims.stub.DelegateConnectionMessageCallback; 35 import android.telephony.ims.stub.DelegateConnectionStateCallback; 36 import android.util.ArraySet; 37 import android.util.Log; 38 import android.util.Pair; 39 40 import androidx.annotation.NonNull; 41 42 import com.android.compatibility.common.util.ShellIdentityUtils; 43 44 import java.util.Set; 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.LinkedBlockingQueue; 47 import java.util.concurrent.TimeUnit; 48 import java.util.stream.Collectors; 49 50 public class TestSipDelegateConnection implements DelegateConnectionStateCallback, 51 DelegateConnectionMessageCallback { 52 53 private interface ExceptionRunnable { run()54 void run() throws Exception; 55 } 56 57 private static final String LOG_TAG = "CtsImsSipDelegateC"; 58 59 public int destroyReason = -1; 60 public SipDelegateConnection connection; 61 public Set<FeatureTagState> deniedTags; 62 public DelegateRegistrationState regState; 63 public SipDelegateConfiguration sipConfig; 64 public final DelegateRequest delegateRequest; 65 66 private int mReceivedMessageErrorResponseReason = -1; 67 private CountDownLatch mLatch; 68 // Pair is <transactionId, error reason> 69 private final LinkedBlockingQueue<SipMessage> mReceivedMessages = new LinkedBlockingQueue<>(); 70 private final LinkedBlockingQueue<Pair<String, Integer>> mSentMessageAcks = 71 new LinkedBlockingQueue<>(); 72 TestSipDelegateConnection(DelegateRequest request)73 public TestSipDelegateConnection(DelegateRequest request) { 74 delegateRequest = request; 75 } 76 connect(SipDelegateManager manager)77 public void connect(SipDelegateManager manager) throws Exception { 78 callUntilImsServiceIsAvailableNoReturn(() -> 79 ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn( 80 manager, (m) -> m.createSipDelegate(delegateRequest, Runnable::run, this, 81 this), ImsException.class, 82 "android.permission.PERFORM_IMS_SINGLE_REGISTRATION")); 83 } 84 disconnect(SipDelegateManager manager, int reason)85 public void disconnect(SipDelegateManager manager, int reason) throws Exception { 86 ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn( 87 manager, (m) -> m.destroySipDelegate(connection, reason), 88 ImsException.class, 89 "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"); 90 } 91 triggerFullNetworkRegistration(SipDelegateManager manager, int sipCode, String sipReason)92 public void triggerFullNetworkRegistration(SipDelegateManager manager, int sipCode, 93 String sipReason) throws Exception { 94 ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn( 95 manager, (m) -> m.triggerFullNetworkRegistration(connection, sipCode, sipReason), 96 ImsException.class, 97 "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"); 98 } 99 100 @Override onMessageReceived(@onNull SipMessage message)101 public void onMessageReceived(@NonNull SipMessage message) { 102 if (ImsUtils.VDBG) Log.d(LOG_TAG, "onMessageReceived"); 103 mReceivedMessages.offer(message); 104 if (mReceivedMessageErrorResponseReason > -1) { 105 connection.notifyMessageReceiveError(message.getViaBranchParameter(), 106 mReceivedMessageErrorResponseReason); 107 } else { 108 connection.notifyMessageReceived(message.getViaBranchParameter()); 109 } 110 } 111 112 @Override onMessageSent(@onNull String viaTransactionId)113 public void onMessageSent(@NonNull String viaTransactionId) { 114 if (ImsUtils.VDBG) Log.d(LOG_TAG, "onMessageSent"); 115 mSentMessageAcks.offer(new Pair<>(viaTransactionId, -1)); 116 } 117 118 @Override onMessageSendFailure(@onNull String viaTransactionId, int reason)119 public void onMessageSendFailure(@NonNull String viaTransactionId, int reason) { 120 if (ImsUtils.VDBG) Log.d(LOG_TAG, "onMessageSendFailure"); 121 mSentMessageAcks.offer(new Pair<>(viaTransactionId, reason)); 122 } 123 124 @Override onCreated(@onNull SipDelegateConnection c)125 public void onCreated(@NonNull SipDelegateConnection c) { 126 if (ImsUtils.VDBG) Log.d(LOG_TAG, "onCreated"); 127 connection = c; 128 mLatch.countDown(); 129 } 130 131 @Override onFeatureTagStatusChanged(@onNull DelegateRegistrationState registrationState, @NonNull Set<FeatureTagState> deniedFeatureTags)132 public void onFeatureTagStatusChanged(@NonNull DelegateRegistrationState registrationState, 133 @NonNull Set<FeatureTagState> deniedFeatureTags) { 134 if (ImsUtils.VDBG) Log.d(LOG_TAG, "onFeatureTagStatusChanged"); 135 regState = registrationState; 136 deniedTags = deniedFeatureTags; 137 mLatch.countDown(); 138 } 139 140 @Override onConfigurationChanged( @onNull SipDelegateConfiguration registeredSipConfig)141 public void onConfigurationChanged( 142 @NonNull SipDelegateConfiguration registeredSipConfig) { 143 if (ImsUtils.VDBG) Log.d(LOG_TAG, "onConfigurationChanged"); 144 sipConfig = registeredSipConfig; 145 mLatch.countDown(); 146 } 147 148 @Override onDestroyed(int reason)149 public void onDestroyed(int reason) { 150 if (ImsUtils.VDBG) Log.d(LOG_TAG, "onDestroyed"); 151 connection = null; 152 destroyReason = reason; 153 mLatch.countDown(); 154 } 155 sendCleanupSession(String callId)156 public void sendCleanupSession(String callId) { 157 assertNotNull("SipDelegate was null when cleaning up session", connection); 158 connection.cleanupSession(callId); 159 } 160 sendMessageAndVerifyCompletedSuccessfully(SipMessage messageToSend)161 public void sendMessageAndVerifyCompletedSuccessfully(SipMessage messageToSend) 162 throws Exception { 163 assertNotNull("SipDelegate was null when sending message", connection); 164 connection.sendMessage(messageToSend, sipConfig.getVersion()); 165 Pair<String, Integer> ack = mSentMessageAcks.poll(ImsUtils.TEST_TIMEOUT_MS, 166 TimeUnit.MILLISECONDS); 167 assertNotNull(ack); 168 assertEquals(messageToSend.getViaBranchParameter(), ack.first); 169 assertNotNull(ack.second); 170 assertEquals(-1, ack.second.intValue()); 171 } 172 sendMessageAndVerifyFailure(SipMessage messageToSend, int expectedReason)173 public void sendMessageAndVerifyFailure(SipMessage messageToSend, int expectedReason) 174 throws Exception { 175 assertNotNull("SipDelegate was null when sending message", connection); 176 // send invalid version if it was not sent. 177 long version = (sipConfig != null) ? sipConfig.getVersion() : -1; 178 connection.sendMessage(messageToSend, version); 179 Pair<String, Integer> ack = mSentMessageAcks.poll(ImsUtils.TEST_TIMEOUT_MS, 180 TimeUnit.MILLISECONDS); 181 assertNotNull(ack); 182 // TODO actually check this, but for now the platform can not inspect SipMessages and send 183 // the real transaction ID. So, just ensure it is null. 184 //assertEquals(ImsUtils.TEST_TRANSACTION_ID, ack.first); 185 assertNotNull(ack.second); 186 assertEquals(expectedReason, ack.second.intValue()); 187 } 188 verifyMessageReceived(SipMessage messageToVerify)189 public void verifyMessageReceived(SipMessage messageToVerify) 190 throws Exception { 191 SipMessage m = mReceivedMessages.poll(ImsUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); 192 assertEquals(messageToVerify, m); 193 } 194 setReceivedMessageErrorResponseReason(int reason)195 public void setReceivedMessageErrorResponseReason(int reason) { 196 mReceivedMessageErrorResponseReason = reason; 197 } 198 verifyDelegateCreated()199 public void verifyDelegateCreated() { 200 assertNotNull("SipDelegate is null when it should have been created", connection); 201 } 202 verifyConfigEquals(SipDelegateConfiguration config)203 public void verifyConfigEquals(SipDelegateConfiguration config) { 204 assertNotNull("SIP configuration should not be null", sipConfig); 205 assertEquals("IMS config version is not correct", config.getVersion(), 206 sipConfig.getVersion()); 207 assertEquals(config, sipConfig); 208 } 209 verifyRegistrationStateRegistered()210 public void verifyRegistrationStateRegistered() { 211 verifyRegistrationStateRegistered(delegateRequest.getFeatureTags()); 212 } 213 verifyRegistrationStateRegistered(Set<String> tags)214 public void verifyRegistrationStateRegistered(Set<String> tags) { 215 assertNotNull(regState); 216 assertFalse("No registered features found", 217 regState.getRegisteredFeatureTags().isEmpty()); 218 ArraySet<String> notRegistered = new ArraySet<>(tags); 219 notRegistered.removeAll(regState.getRegisteredFeatureTags()); 220 assertTrue("Not all requested features were registered: " + notRegistered, 221 notRegistered.isEmpty()); 222 assertTrue(regState.getDeregisteringFeatureTags().isEmpty()); 223 assertTrue(regState.getDeregisteredFeatureTags().isEmpty()); 224 assertTrue(regState.getRegisteringFeatureTags().isEmpty()); 225 } 226 verifyRegistrationStateEmpty()227 public void verifyRegistrationStateEmpty() { 228 assertNotNull(regState); 229 assertTrue(regState.getRegisteredFeatureTags().isEmpty()); 230 assertTrue(regState.getDeregisteringFeatureTags().isEmpty()); 231 assertTrue(regState.getDeregisteredFeatureTags().isEmpty()); 232 assertTrue(regState.getRegisteringFeatureTags().isEmpty()); 233 } 234 verifyRegistrationStateEquals(DelegateRegistrationState s)235 public void verifyRegistrationStateEquals(DelegateRegistrationState s) { 236 assertEquals("unexpected registered tags", s.getRegisteredFeatureTags(), 237 regState.getRegisteredFeatureTags()); 238 assertEquals("unexpected deregistering tags", s.getDeregisteringFeatureTags(), 239 regState.getDeregisteringFeatureTags()); 240 assertEquals("unexpected deregistered tags", s.getDeregisteredFeatureTags(), 241 regState.getDeregisteredFeatureTags()); 242 assertEquals("unexpected registering tags", s.getRegisteringFeatureTags(), 243 regState.getRegisteringFeatureTags()); 244 } 245 verifyDeregisteringStateContains(String featureTag, int state)246 public boolean verifyDeregisteringStateContains(String featureTag, int state) { 247 for (FeatureTagState s : regState.getDeregisteringFeatureTags()) { 248 if (s.getFeatureTag().equals(featureTag) && s.getState() == state) { 249 return true; 250 } 251 } 252 return false; 253 } 254 255 verifyNoneDenied()256 public void verifyNoneDenied() { 257 assertNotNull(deniedTags); 258 assertTrue(deniedTags.isEmpty()); 259 } 260 verifyDenied(Set<FeatureTagState> denied)261 public void verifyDenied(Set<FeatureTagState> denied) { 262 assertNotNull(deniedTags); 263 assertEquals(denied, deniedTags); 264 } 265 verifyAllDenied(int reason)266 public void verifyAllDenied(int reason) { 267 assertNotNull(deniedTags); 268 // Ensure that if the request is empty, the denied tags are also empty. 269 if (delegateRequest.getFeatureTags().isEmpty()) { 270 assertTrue(deniedTags.isEmpty()); 271 } 272 // All should be denied with the same reason. 273 FeatureTagState incorrectReason = deniedTags.stream().filter((t) -> t.getState() != reason) 274 .findAny().orElse(null); 275 Set<String> deniedFeatures = deniedTags.stream().map(FeatureTagState::getFeatureTag) 276 .collect(Collectors.toSet()); 277 assertNull(incorrectReason); 278 279 Set<String> requestedTags = new ArraySet<>(delegateRequest.getFeatureTags()); 280 requestedTags.removeAll(deniedFeatures); 281 assertTrue("Not all tags denied: " + requestedTags, requestedTags.isEmpty()); 282 } 283 verifyDestroyed(int reason)284 public void verifyDestroyed(int reason) { 285 assertEquals(reason, destroyReason); 286 } 287 288 /** 289 * Set the number of operations that are expected to happen. Use {@link #waitForCountDown(long)} 290 * to wait for the operations to occur. 291 */ setOperationCountDownLatch(int operationCount)292 public void setOperationCountDownLatch(int operationCount) { 293 if (ImsUtils.VDBG) Log.d(LOG_TAG, "setOperationCountDownLatch: " + operationCount); 294 mLatch = new CountDownLatch(operationCount); 295 } 296 297 /** 298 * Wait for the latch set in {@link #setOperationCountDownLatch(int)} to complete. 299 * @param timeoutMs The time to wait before giving up. 300 * @return {@code true} if the latch successfully counted down, {@code false} if time elaptsed 301 * before it counted down. 302 */ waitForCountDown(long timeoutMs)303 public boolean waitForCountDown(long timeoutMs) { 304 while (mLatch.getCount() > 0) { 305 try { 306 return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS); 307 } catch (InterruptedException ignore) { } 308 } 309 return true; 310 } 311 312 /** 313 * Wait up to five seconds (retrying a command 1 time per second) until ImsExceptions due to the 314 * ImsService not being available go away. 315 */ callUntilImsServiceIsAvailableNoReturn(ExceptionRunnable command)316 private void callUntilImsServiceIsAvailableNoReturn(ExceptionRunnable command) 317 throws Exception { 318 int retry = 0; 319 while (retry < 5) { 320 try { 321 command.run(); 322 return; 323 } catch (ImsException e) { 324 // we want to absorb only the unavailable error, as telephony may still be 325 // internally setting up. Any other type of ImsException is unexpected. 326 if (e.getCode() != ImsException.CODE_ERROR_SERVICE_UNAVAILABLE) { 327 throw e; 328 } 329 } 330 Thread.sleep(1000); 331 retry++; 332 } 333 } 334 } 335