1 /*
2  * Copyright (C) 2023 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.telecom.cts.apps;
18 
19 import static android.os.SystemClock.sleep;
20 import static android.telecom.cts.apps.StackTraceUtil.appendStackTraceList;
21 import static org.junit.Assert.assertEquals;
22 
23 import android.os.SystemClock;
24 
25 import android.telecom.Connection;
26 
27 import java.util.List;
28 import java.util.Objects;
29 
30 public class WaitUntil {
31     public static final long DEFAULT_TIMEOUT_MS = 10000;
32     private static final String CLASS_NAME = WaitUntil.class.getCanonicalName();
33     private static final String TELECOM_ID_TOKEN = "_";
34 
35     // NOTE:
36     // - This method should NOT be called from a telecom test app. The assertEquals will cause
37     //     a DeadObjectException which will make any test failure log unreadable!
38     // - This can be used for classes like BindUtils, BaseAppVerifierImpl, etc. that are running
39     //    in the CTS test process
waitUntilConditionIsTrueOrTimeout( Condition condition, long timeout, String description)40     public static void waitUntilConditionIsTrueOrTimeout(
41             Condition condition,
42             long timeout,
43             String description) {
44 
45         long startTimeMillis = SystemClock.elapsedRealtime();
46         long remainingTimeMillis = timeout;
47         long elapsedTimeMillis;
48 
49         while (!Objects.equals(condition.expected(), condition.actual())
50                 && remainingTimeMillis > 0) {
51             sleep(50);
52             elapsedTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
53             remainingTimeMillis = timeout - elapsedTimeMillis;
54         }
55         assertEquals(description, condition.expected(), condition.actual());
56     }
57 
58     // NOTE:
59     // - This method should NOT be called from a telecom test app. The assertEquals will cause
60     //     a DeadObjectException which will make any test failure log unreadable!
61     // - This can be used for classes like BindUtils, BaseAppVerifierImpl, etc. that are running
62     //    in the CTS test process
waitUntilConditionIsTrueOrTimeout( Condition condition)63     public static void waitUntilConditionIsTrueOrTimeout(
64             Condition condition) {
65         long startTimeMillis = SystemClock.elapsedRealtime();
66         long remainingTimeMillis = DEFAULT_TIMEOUT_MS;
67         long elapsedTimeMillis;
68 
69         while (!Objects.equals(condition.expected(), condition.actual())
70                 && remainingTimeMillis > 0) {
71             sleep(50);
72             elapsedTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
73             remainingTimeMillis = DEFAULT_TIMEOUT_MS - elapsedTimeMillis;
74         }
75         assertEquals(condition.expected(), condition.actual());
76     }
77 
78     // This helper is intended for test apps!
waitUntilConditionIsTrueOrReturnFalse( Condition condition)79     private static boolean waitUntilConditionIsTrueOrReturnFalse(
80             Condition condition) {
81         long startTimeMillis = SystemClock.elapsedRealtime();
82         long remainingTimeMillis = DEFAULT_TIMEOUT_MS;
83         long elapsedTimeMillis;
84 
85         while (!Objects.equals(condition.expected(), condition.actual())
86                 && remainingTimeMillis > 0) {
87             sleep(50);
88             elapsedTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
89             remainingTimeMillis = DEFAULT_TIMEOUT_MS - elapsedTimeMillis;
90         }
91         return (condition.expected().equals(condition.actual()));
92     }
93 
94 
95     public interface ConnectionServiceImpl {
getLastConnection()96         Connection getLastConnection();
97     }
98 
waitUntilIdIsSet( String packageName, List<String> stackTrace, Connection connection)99     public static String waitUntilIdIsSet(
100             String packageName,
101             List<String> stackTrace,
102             Connection connection) {
103 
104         boolean success = waitUntilConditionIsTrueOrReturnFalse(
105                 new Condition() {
106                     @Override
107                     public Object expected() {
108                         return true;
109                     }
110 
111                     @Override
112                     public Object actual() {
113                         return connection.getTelecomCallId() != null
114                                 && !(connection.getTelecomCallId().equals(""));
115                     }
116                 }
117         );
118 
119         if (!success) {
120             throw new TestAppException(packageName,
121                     appendStackTraceList(stackTrace,
122                             CLASS_NAME + ".waitUntilIdIsSet"),
123                     "expected:<Connection#getCallId() to return an id within the time window>"
124                             + "actual:<hit timeout waiting for the Connection#getCallId() to be "
125                             + "set>");
126         }
127 
128         return extractTelecomId(connection);
129     }
130 
waitUntilCallAudioStateIsSet( String packageName, List<String> stackTrace, boolean isManaged, Connection connection)131     public static void waitUntilCallAudioStateIsSet(
132             String packageName,
133             List<String> stackTrace,
134             boolean isManaged,
135             Connection connection) {
136         boolean success = waitUntilConditionIsTrueOrReturnFalse(
137                 new Condition() {
138                     @Override
139                     public Object expected() {
140                         return true;
141                     }
142 
143                     @Override
144                     public Object actual() {
145                         if (isManaged) {
146                             return ((ManagedConnection) connection).getCurrentCallEndpointFromCallback()
147                                     != null;
148                         } else {
149                             return ((VoipConnection) connection).getCurrentCallEndpointFromCallback()
150                                     != null;
151                         }
152                     }
153                 }
154         );
155 
156         if (!success) {
157             throw new TestAppException(packageName,
158                     appendStackTraceList(stackTrace,
159                             CLASS_NAME + ".waitUntilCallAudioStateIsSet"),
160                     "expected:<Connection#onCallEndpointChanged() to set"
161                             + " Connection#mCallEndpoints within the time window> "
162                             + "actual:<hit timeout waiting for the CallEndpoint to be set>");
163         }
164     }
165 
waitUntilAvailableEndpointsIsSet( String packageName, List<String> stackTrace, boolean isManaged, Connection connection)166     public static void waitUntilAvailableEndpointsIsSet(
167             String packageName,
168             List<String> stackTrace,
169             boolean isManaged,
170             Connection connection) {
171         boolean success = waitUntilConditionIsTrueOrReturnFalse(
172                 new Condition() {
173                     @Override
174                     public Object expected() {
175                         return true;
176                     }
177 
178                     @Override
179                     public Object actual() {
180                         if (isManaged) {
181                             return ((ManagedConnection) connection).getCallEndpoints()
182                                     != null;
183                         } else {
184                             return ((VoipConnection) connection).getCallEndpoints()
185                                     != null;
186                         }
187                     }
188                 }
189         );
190 
191         if (!success) {
192             throw new TestAppException(packageName,
193                     appendStackTraceList(stackTrace,
194                             CLASS_NAME + ".waitUntilAvailableEndpointsIsSet"),
195                     "expected:<Connection#onAvailableCallEndpointsChanged() to set"
196                             + " ManagedConnection#mCallEndpoints within the time window> "
197                             + "actual:<hit timeout waiting for the CallEndpoints to be set>");
198         }
199     }
200 
waitUntilConnectionIsNonNull( String packageName, List<String> stackTrace, ConnectionServiceImpl s)201     public static Connection waitUntilConnectionIsNonNull(
202             String packageName,
203             List<String> stackTrace,
204             ConnectionServiceImpl s) {
205 
206         boolean success = waitUntilConditionIsTrueOrReturnFalse(
207                 new Condition() {
208                     @Override
209                     public Object expected() {
210                         return true;
211                     }
212 
213                     @Override
214                     public Object actual() {
215                         return getLastConnection(s) != null;
216                     }
217                 }
218         );
219 
220         if (!success) {
221             throw new TestAppException(packageName,
222                     appendStackTraceList(stackTrace,
223                             CLASS_NAME + ".waitUntilConnectionIsNonNull_Voip"),
224                     "expected:<Connection to be added to the ConnectionService> "
225                             + "actual:<hit timeout waiting for Connection>");
226         }
227 
228         return getLastConnection(s);
229     }
230 
extractTelecomId(Connection connection)231     private static String extractTelecomId(Connection connection) {
232         String str = connection.getTelecomCallId();
233         return str.substring(0, str.indexOf(TELECOM_ID_TOKEN));
234     }
235 
getLastConnection(ConnectionServiceImpl s)236     private static Connection getLastConnection(ConnectionServiceImpl s) {
237         return s.getLastConnection();
238     }
239 
waitUntilCurrentCallEndpointIsSet( String packageName, List<String> stackTrace, TransactionalCallEvents events)240     public static void waitUntilCurrentCallEndpointIsSet(
241             String packageName,
242             List<String> stackTrace,
243             TransactionalCallEvents events) throws TestAppException {
244         boolean success = WaitUntil.waitUntilConditionIsTrueOrReturnFalse(
245                 new Condition() {
246                     @Override
247                     public Object expected() {
248                         return true;
249                     }
250 
251                     @Override
252                     public Object actual() {
253                         return events.getCurrentCallEndpoint() != null;
254                     }
255                 }
256         );
257 
258         if (!success) {
259             throw new TestAppException(packageName,
260                     appendStackTraceList(stackTrace,
261                             CLASS_NAME + ".waitUntilCurrentCallEndpointIsSet"),
262                     "expected:<TransactionalCallEvents#onCallEndpointChanged() to set"
263                             + " TransactionalCallEvents#mCurrentCallEndpoint within time window>"
264                             + " actual:<hit timeout waiting for the CallEndpoint to be set>");
265         }
266     }
267 
waitUntilAvailableEndpointAreSet( String packageName, List<String> stackTrace, TransactionalCallEvents events)268     public static void waitUntilAvailableEndpointAreSet(
269             String packageName,
270             List<String> stackTrace,
271             TransactionalCallEvents events) throws TestAppException {
272 
273         boolean success = WaitUntil.waitUntilConditionIsTrueOrReturnFalse(
274                 new Condition() {
275                     @Override
276                     public Object expected() {
277                         return true;
278                     }
279 
280                     @Override
281                     public Object actual() {
282                         return events.getCallEndpoints() != null;
283                     }
284                 }
285         );
286 
287         if (!success) {
288             throw new TestAppException(packageName,
289                     appendStackTraceList(stackTrace,
290                             CLASS_NAME + ".waitUntilAvailableEndpointAreSet"),
291                     "expected:<TransactionalCallEvents#onAvailableCallEndpointsChanged() to set"
292                             + " TransactionalCallEvents#mCallEndpoints within the time window> "
293                             + "actual:<hit timeout waiting for the CallEndpoints to be set>");
294         }
295     }
296 }
297