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