• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.webkit.cts;
18 
19 import static org.junit.Assert.*;
20 
21 import android.app.Instrumentation;
22 import android.app.UiAutomation;
23 import android.content.Context;
24 import android.os.StrictMode;
25 import android.os.StrictMode.ThreadPolicy;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.webkit.WebView;
30 import android.widget.FrameLayout;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.test.InstrumentationRegistry;
35 
36 import org.apache.http.util.EncodingUtils;
37 
38 import java.io.ByteArrayInputStream;
39 import java.security.cert.CertificateFactory;
40 import java.security.cert.X509Certificate;
41 import java.util.List;
42 
43 import javax.net.ssl.X509TrustManager;
44 
45 /**
46  * This class contains all the environmental variables that need to be configured for WebView tests
47  * to either run inside the SDK Runtime or within an Activity.
48  */
49 public final class SharedWebViewTestEnvironment {
50     @Nullable private final Context mContext;
51     @Nullable private final WebView mWebView;
52     @Nullable private final FrameLayout mRootLayout;
53     private final IHostAppInvoker mHostAppInvoker;
54 
SharedWebViewTestEnvironment( Context context, WebView webView, IHostAppInvoker hostAppInvoker, FrameLayout rootLayout)55     private SharedWebViewTestEnvironment(
56             Context context,
57             WebView webView,
58             IHostAppInvoker hostAppInvoker,
59             FrameLayout rootLayout) {
60         mContext = context;
61         mWebView = webView;
62         mHostAppInvoker = hostAppInvoker;
63         mRootLayout = rootLayout;
64     }
65 
66     @Nullable
getContext()67     public Context getContext() {
68         return mContext;
69     }
70 
71     @Nullable
getWebView()72     public WebView getWebView() {
73         return mWebView;
74     }
75 
76     /**
77      * Some tests require adding a content view to the root view at runtime. This method mimics the
78      * behaviour of Activity.addContentView()
79      */
80     @Nullable
addContentView(View view, ViewGroup.LayoutParams params)81     public void addContentView(View view, ViewGroup.LayoutParams params) {
82         view.setLayoutParams(params);
83         mRootLayout.addView(view);
84     }
85 
86     /**
87      * Apache Utils can't be statically linked so we can't use them directly inside the SDK Runtime.
88      * Use this method instead of EncodingUtils.getBytes.
89      */
getEncodingBytes(String data, String charset)90     public byte[] getEncodingBytes(String data, String charset) {
91         return ExceptionWrapper.unwrap(() -> {
92             return mHostAppInvoker.getEncodingBytes(data, charset);
93         });
94     }
95 
96     /** Invokes waitForIdleSync on the {@link Instrumentation} in the activity. */
waitForIdleSync()97     public void waitForIdleSync() {
98         ExceptionWrapper.unwrap(() -> {
99             mHostAppInvoker.waitForIdleSync();
100         });
101     }
102 
103     /** Invokes sendKeyDownUpSync on the {@link Instrumentation} in the activity. */
sendKeyDownUpSync(int keyCode)104     public void sendKeyDownUpSync(int keyCode) {
105         ExceptionWrapper.unwrap(() -> {
106             mHostAppInvoker.sendKeyDownUpSync(keyCode);
107         });
108     }
109 
110     /** Invokes sendPointerSync on the {@link Instrumentation} in the activity. */
sendPointerSync(MotionEvent event)111     public void sendPointerSync(MotionEvent event) {
112         ExceptionWrapper.unwrap(() -> {
113             mHostAppInvoker.sendPointerSync(event);
114         });
115     }
116 
117     /** Returns a web server that can be used for web based testing. */
getWebServer()118     public SharedSdkWebServer getWebServer() {
119         return ExceptionWrapper.unwrap(() -> {
120             return new SharedSdkWebServer(mHostAppInvoker.getWebServer());
121         });
122     }
123 
124     /** Returns a web server that has been started and can be used for web based testing. */
125     public SharedSdkWebServer getSetupWebServer(@SslMode int sslMode) {
126         return getSetupWebServer(sslMode, null, 0, 0);
127     }
128 
129     /** Returns a web server that has been started and can be used for web based testing. */
130     public SharedSdkWebServer getSetupWebServer(
131             @SslMode int sslMode, @Nullable byte[] acceptedIssuerDer, int keyResId, int certResId) {
132         SharedSdkWebServer webServer = getWebServer();
133         webServer.start(sslMode, acceptedIssuerDer, keyResId, certResId);
134         return webServer;
135     }
136 
137     /**
138      * Use this builder to create a {@link SharedWebViewTestEnvironment}. The {@link
139      * SharedWebViewTestEnvironment} can not be built directly.
140      */
141     public static final class Builder {
142         private Context mContext;
143         private WebView mWebView;
144 
145         private FrameLayout mRootLayout;
146         private IHostAppInvoker mHostAppInvoker;
147 
148         /** Provide a {@link Context} the tests should use for your environment. */
149         public Builder setContext(@NonNull Context context) {
150             mContext = context;
151             return this;
152         }
153 
154         /** Provide a {@link WebView} the tests should use for your environment. */
155         public Builder setWebView(@NonNull WebView webView) {
156             mWebView = webView;
157             return this;
158         }
159 
160         /**
161          * Provide a {@link IHostAppInvoker} the tests should use for your environment.
162          *
163          * <p>This can be created with {@link createHostAppInvoker}.
164          *
165          * <p>Note: This is required.
166          */
167         public Builder setHostAppInvoker(@NonNull IHostAppInvoker hostAppInvoker) {
168             mHostAppInvoker = hostAppInvoker;
169             return this;
170         }
171 
172         /** Provide a {@link FrameLayout} the tests should use for your environment. */
173         public Builder setRootLayout(@NonNull FrameLayout rootLayout) {
174             mRootLayout = rootLayout;
175             return this;
176         }
177 
178         /** Build a new SharedWebViewTestEnvironment. */
179         public SharedWebViewTestEnvironment build() {
180             if (mHostAppInvoker == null) {
181                 throw new NullPointerException("The host app invoker is required");
182             }
183             return new SharedWebViewTestEnvironment(
184                     mContext, mWebView, mHostAppInvoker, mRootLayout);
185         }
186     }
187 
188     /**
189      * UiAutomation sends events at device level which lets us get around issues with sending
190      * instrumented events to the SDK Runtime but we don't want this for the regular tests. If
191      * something like a dialog pops up while an input event is being sent, the instrumentation would
192      * treat that as an issue while the UiAutomation input event would just send it through.
193      *
194      * <p>So by default, we disable this use and only use it in the SDK Sandbox.
195      *
196      * <p>This API is used for regular activity based tests.
197      */
198     public static IHostAppInvoker.Stub createHostAppInvoker(Context applicationContext) {
199         return createHostAppInvoker(applicationContext, false);
200     }
201     /**
202      * This will generate a new {@link IHostAppInvoker} binder node. This should be called from
203      * wherever the activity exists for test cases.
204      */
205     public static IHostAppInvoker.Stub createHostAppInvoker(
206             Context applicationContext, boolean allowUiAutomation) {
207         return new IHostAppInvoker.Stub() {
208             private Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
209             private UiAutomation mUiAutomation;
210 
211             public void waitForIdleSync() {
212                 ExceptionWrapper.wrap(() -> {
213                     mInstrumentation.waitForIdleSync();
214                 });
215             }
216 
217             public void sendKeyDownUpSync(int keyCode) {
218                 ExceptionWrapper.wrap(() -> {
219                     mInstrumentation.sendKeyDownUpSync(keyCode);
220                 });
221             }
222 
223             public void sendPointerSync(MotionEvent event) {
224                 ExceptionWrapper.wrap(() -> {
225                     if (allowUiAutomation) {
226                         sendPointerSyncWithUiAutomation(event);
227                     } else {
228                         sendPointerSyncWithInstrumentation(event);
229                     }
230                 });
231             }
232 
233             public byte[] getEncodingBytes(String data, String charset) {
234                 return ExceptionWrapper.wrap(() -> {
235                     return EncodingUtils.getBytes(data, charset);
236                 });
237             }
238 
239             public IWebServer getWebServer() {
240                 return new IWebServer.Stub() {
241                     private CtsTestServer mWebServer;
242 
243                     public void start(
244                             @SslMode int sslMode,
245                             @Nullable byte[] acceptedIssuerDer,
246                             int keyResId,
247                             int certResId) {
248                         ExceptionWrapper.wrap(() -> {
249                             assertNull(mWebServer);
250                             final X509Certificate[] acceptedIssuerCerts;
251                             if (acceptedIssuerDer != null) {
252                                 CertificateFactory certFactory =
253                                         CertificateFactory.getInstance("X.509");
254                                 acceptedIssuerCerts = new X509Certificate[] {
255                                     (X509Certificate) certFactory.generateCertificate(
256                                             new ByteArrayInputStream(acceptedIssuerDer))
257                                 };
258                             } else {
259                                 acceptedIssuerCerts = null;
260                             }
261                             X509TrustManager trustManager =
262                                     new CtsTestServer.CtsTrustManager() {
263                                         @Override
264                                         public X509Certificate[] getAcceptedIssuers() {
265                                             return acceptedIssuerCerts;
266                                         }
267                                     };
268                             mWebServer = new CtsTestServer(
269                                     applicationContext, sslMode, trustManager, keyResId, certResId);
270                         });
271                     }
272 
273                     public void shutdown() {
274                         if (mWebServer == null) {
275                             return;
276                         }
277                         ExceptionWrapper.wrap(() -> {
278                             ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
279                             ThreadPolicy tmpPolicy =
280                                     new ThreadPolicy.Builder(oldPolicy)
281                                             .permitNetwork()
282                                             .build();
283                             StrictMode.setThreadPolicy(tmpPolicy);
284                             mWebServer.shutdown();
285                             mWebServer = null;
286                             StrictMode.setThreadPolicy(oldPolicy);
287                         });
288                     }
289 
290                     public void resetRequestState() {
291                         ExceptionWrapper.wrap(() -> {
292                             assertNotNull("The WebServer needs to be started", mWebServer);
293                             mWebServer.resetRequestState();
294                         });
295                     }
296 
297                     public String setResponse(
298                             String path, String responseString, List<HttpHeader> responseHeaders) {
299                         return ExceptionWrapper.wrap(() -> {
300                             assertNotNull("The WebServer needs to be started", mWebServer);
301                             return mWebServer.setResponse(
302                                     path, responseString, HttpHeader.asPairList(responseHeaders));
303                         });
304                     }
305 
306                     public String getAbsoluteUrl(String path) {
307                         return ExceptionWrapper.wrap(() -> {
308                             assertNotNull("The WebServer needs to be started", mWebServer);
309                             return mWebServer.getAbsoluteUrl(path);
310                         });
311                     }
312 
313                     public String getUserAgentUrl() {
314                         return ExceptionWrapper.wrap(() -> {
315                             assertNotNull("The WebServer needs to be started", mWebServer);
316                             return mWebServer.getUserAgentUrl();
317                         });
318                     }
319 
320                     public String getDelayedAssetUrl(String path) {
321                         return ExceptionWrapper.wrap(() -> {
322                             assertNotNull("The WebServer needs to be started", mWebServer);
323                             return mWebServer.getDelayedAssetUrl(path);
324                         });
325                     }
326 
327                     public String getRedirectingAssetUrl(String path) {
328                         return ExceptionWrapper.wrap(() -> {
329                             assertNotNull("The WebServer needs to be started", mWebServer);
330                             return mWebServer.getRedirectingAssetUrl(path);
331                         });
332                     }
333 
334                     public String getAssetUrl(String path) {
335                         return ExceptionWrapper.wrap(() -> {
336                             assertNotNull("The WebServer needs to be started", mWebServer);
337                             return mWebServer.getAssetUrl(path);
338                         });
339                     }
340 
341                     public String getAuthAssetUrl(String path) {
342                         return ExceptionWrapper.wrap(() -> {
343                             assertNotNull("The WebServer needs to be started", mWebServer);
344                             return mWebServer.getAuthAssetUrl(path);
345                         });
346                     }
347 
348                     public String getBinaryUrl(String mimeType, int contentLength) {
349                         return ExceptionWrapper.wrap(() -> {
350                             assertNotNull("The WebServer needs to be started", mWebServer);
351                             return mWebServer.getBinaryUrl(mimeType, contentLength);
352                         });
353                     }
354 
355                     public String getAppCacheUrl() {
356                         return ExceptionWrapper.wrap(() -> {
357                             assertNotNull("The WebServer needs to be started", mWebServer);
358                             return mWebServer.getAppCacheUrl();
359                         });
360                     }
361 
362                     public int getRequestCount() {
363                         return ExceptionWrapper.wrap(() -> {
364                             assertNotNull("The WebServer needs to be started", mWebServer);
365                             return mWebServer.getRequestCount();
366                         });
367                     }
368 
369                     public int getRequestCountWithPath(String path) {
370                         return ExceptionWrapper.wrap(() -> {
371                             assertNotNull("The WebServer needs to be started", mWebServer);
372                             return mWebServer.getRequestCount(path);
373                         });
374                     }
375 
376                     public boolean wasResourceRequested(String url) {
377                         return ExceptionWrapper.wrap(() -> {
378                             assertNotNull("The WebServer needs to be started", mWebServer);
379                             return mWebServer.wasResourceRequested(url);
380                         });
381                     }
382 
383                     public HttpRequest getLastRequest(String path) {
384                         return ExceptionWrapper.wrap(() -> {
385                             assertNotNull("The WebServer needs to be started", mWebServer);
386                             return toHttpRequest(path, mWebServer.getLastRequest(path));
387                         });
388                     }
389 
390                     public HttpRequest getLastAssetRequest(String url) {
391                         return ExceptionWrapper.wrap(() -> {
392                             assertNotNull("The WebServer needs to be started", mWebServer);
393                             return toHttpRequest(url, mWebServer.getLastAssetRequest(url));
394                         });
395                     }
396 
397                     public String getCookieUrl(String path) {
398                         return ExceptionWrapper.wrap(() -> {
399                             assertNotNull("The WebServer needs to be started", mWebServer);
400                             return mWebServer.getCookieUrl(path);
401                         });
402                     }
403 
404                     public String getSetCookieUrl(
405                             String path, String key, String value, String attributes) {
406                         return ExceptionWrapper.wrap(() -> {
407                             assertNotNull("The WebServer needs to be started", mWebServer);
408                             return mWebServer.getSetCookieUrl(path, key, value, attributes);
409                         });
410                     }
411 
412                     public String getLinkedScriptUrl(String path, String url) {
413                         return ExceptionWrapper.wrap(() -> {
414                             assertNotNull("The WebServer needs to be started", mWebServer);
415                             return mWebServer.getLinkedScriptUrl(path, url);
416                         });
417                     }
418 
419                     private HttpRequest toHttpRequest(
420                             String url, org.apache.http.HttpRequest apacheRequest) {
421                         if (apacheRequest == null) {
422                             return null;
423                         }
424 
425                         return new HttpRequest(url, apacheRequest);
426                     }
427                 };
428             }
429 
430             private void sendPointerSyncWithInstrumentation(MotionEvent event) {
431                 mInstrumentation.sendPointerSync(event);
432             }
433 
434             private void sendPointerSyncWithUiAutomation(MotionEvent event) {
435                 if (mUiAutomation == null) {
436                     mUiAutomation = mInstrumentation.getUiAutomation();
437 
438                     if (mUiAutomation == null) {
439                         fail("Could not retrieve UI automation");
440                     }
441                 }
442 
443                 if (!mUiAutomation.injectInputEvent(event, true)) {
444                     fail("Could not inject motion event");
445                 }
446             }
447         };
448     }
449 }
450