xref: /aosp_15_r20/cts/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2012 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.assertFalse;
20 
21 import android.content.Context;
22 import android.location.Criteria;
23 import android.location.Location;
24 import android.location.LocationManager;
25 import android.location.LocationProvider;
26 import android.os.SystemClock;
27 import android.platform.test.annotations.AppModeFull;
28 import android.webkit.GeolocationPermissions;
29 import android.webkit.JavascriptInterface;
30 import android.webkit.WebResourceResponse;
31 import android.webkit.WebView;
32 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
33 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
34 
35 import androidx.test.ext.junit.rules.ActivityScenarioRule;
36 import androidx.test.ext.junit.runners.AndroidJUnit4;
37 import androidx.test.filters.MediumTest;
38 import androidx.test.platform.app.InstrumentationRegistry;
39 
40 import com.android.compatibility.common.util.LocationUtils;
41 import com.android.compatibility.common.util.NullWebViewUtils;
42 import com.android.compatibility.common.util.PollingCheck;
43 
44 import org.junit.After;
45 import org.junit.Assume;
46 import org.junit.Before;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 
51 import java.io.ByteArrayInputStream;
52 import java.io.UnsupportedEncodingException;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Set;
56 import java.util.TreeSet;
57 import java.util.concurrent.Callable;
58 
59 @AppModeFull(reason = "Instant apps do not have access to location information")
60 @MediumTest
61 @RunWith(AndroidJUnit4.class)
62 public class GeolocationTest {
63 
64     // TODO Write additional tests to cover:
65     // - test that the errors are correct
66     // - test that use of gps and network location is correct
67 
68     // The URLs does not matter since the tests will intercept the load, but it has to be a real
69     // url, and different domains.
70     private static final String URL_1 = "https://www.example.com";
71     private static final String URL_2 = "https://www.example.org";
72     private static final String URL_INSECURE = "http://www.example.org";
73 
74     private static final String JS_INTERFACE_NAME = "Android";
75     private static final int POLLING_TIMEOUT = 60 * 1000;
76     private static final int LOCATION_THREAD_UPDATE_WAIT_MS = 250;
77 
78     // static HTML page always injected instead of the url loaded
79     private static final String RAW_HTML =
80             "<!DOCTYPE html>\n" +
81             "<html>\n" +
82             "  <head>\n" +
83             "    <title>Geolocation</title>\n" +
84             "    <script>\n" +
85             "      function gotPos(position) {\n" +
86             "        " + JS_INTERFACE_NAME + ".gotLocation();\n" +
87             "      }\n" +
88             "      function initiate_getCurrentPosition() {\n" +
89             "        navigator.geolocation.getCurrentPosition(\n" +
90             "            gotPos,\n" +
91             "            handle_errors,\n" +
92             "            {maximumAge:1000});\n" +
93             "      }\n" +
94             "      function handle_errors(error) {\n" +
95             "        switch(error.code) {\n" +
96             "          case error.PERMISSION_DENIED:\n" +
97             "            " + JS_INTERFACE_NAME + ".errorDenied(); break;\n" +
98             "          case error.POSITION_UNAVAILABLE:\n" +
99             "            " + JS_INTERFACE_NAME + ".errorUnavailable(); break;\n" +
100             "          case error.TIMEOUT:\n" +
101             "            " + JS_INTERFACE_NAME + ".errorTimeout(); break;\n" +
102             "          default: break;\n" +
103             "        }\n" +
104             "      }\n" +
105             "    </script>\n" +
106             "  </head>\n" +
107             "  <body onload=\"initiate_getCurrentPosition();\">\n" +
108             "  </body>\n" +
109             "</html>";
110 
111     @Rule
112     public ActivityScenarioRule mActivityScenarioRule =
113             new ActivityScenarioRule(WebViewCtsActivity.class);
114 
115     private JavascriptStatusReceiver mJavascriptStatusReceiver;
116     private LocationManager mLocationManager;
117     private WebViewOnUiThread mOnUiThread;
118     private Thread mLocationUpdateThread;
119     private volatile boolean mLocationUpdateThreadExitRequested;
120     private List<String> mProviders;
121 
122     // Both this test and WebViewOnUiThread need to override some of the methods on WebViewClient,
123     // so this test sublclasses the WebViewClient from WebViewOnUiThread
124     private static class InterceptClient extends WaitForLoadedClient {
125 
InterceptClient(WebViewOnUiThread webViewOnUiThread)126         public InterceptClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
127             super(webViewOnUiThread);
128         }
129 
130         @Override
shouldInterceptRequest(WebView view, String url)131         public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
132             // Intercept all page loads with the same geolocation enabled page
133             try {
134                 return new WebResourceResponse("text/html", "utf-8",
135                     new ByteArrayInputStream(RAW_HTML.getBytes("UTF-8")));
136             } catch (UnsupportedEncodingException e) {
137                 return null;
138             }
139         }
140     }
141 
142     @Before
setUp()143     public void setUp() throws Exception {
144         Assume.assumeTrue("WebView is not available", NullWebViewUtils.isWebViewAvailable());
145         mActivityScenarioRule.getScenario().onActivity(activity -> {
146             WebViewCtsActivity webViewCtsActivity = (WebViewCtsActivity) activity;
147             WebView webview = webViewCtsActivity.getWebView();
148             if (webview != null) {
149                 mOnUiThread = new WebViewOnUiThread(webview);
150             }
151         });
152         LocationUtils.registerMockLocationProvider(
153                 InstrumentationRegistry.getInstrumentation(), true);
154 
155         if (mOnUiThread != null) {
156             // Set up a WebView with JavaScript and Geolocation enabled
157             final String GEO_DIR = "geo_test";
158             mOnUiThread.getSettings().setJavaScriptEnabled(true);
159             mOnUiThread.getSettings().setGeolocationEnabled(true);
160             mOnUiThread.getSettings().setGeolocationDatabasePath(
161                     InstrumentationRegistry.getInstrumentation().getContext().getDir(GEO_DIR, 0)
162                     .getPath());
163 
164             // Add a JsInterface to report back to the test when a location is received
165             mJavascriptStatusReceiver = new JavascriptStatusReceiver();
166             mOnUiThread.addJavascriptInterface(mJavascriptStatusReceiver, JS_INTERFACE_NAME);
167 
168             // Always intercept all loads with the same geolocation test page
169             mOnUiThread.setWebViewClient(new InterceptClient(mOnUiThread));
170             // Clear all permissions before each test
171             GeolocationPermissions.getInstance().clearAll();
172             // Cache this mostly because the lookup is two lines of code
173             mLocationManager = (LocationManager) InstrumentationRegistry.getInstrumentation()
174                     .getContext().getSystemService(Context.LOCATION_SERVICE);
175             // Add a test provider before each test to inject a location
176             mProviders = mLocationManager.getAllProviders();
177             for (String provider : mProviders) {
178                 // Can't mock passive provider.
179                 if (provider.equals(LocationManager.PASSIVE_PROVIDER)) {
180                     mProviders.remove(provider);
181                     break;
182                 }
183             }
184             if(mProviders.size() == 0)
185             {
186                 addTestLocationProvider();
187                 mAddedTestLocationProvider = true;
188             }
189             mProviders.add(LocationManager.FUSED_PROVIDER);
190             addTestProviders();
191         }
192     }
193 
194     @After
tearDown()195     public void tearDown() throws Exception {
196         stopUpdateLocationThread();
197         if (mProviders != null) {
198             // Remove the test provider after each test
199             for (String provider : mProviders) {
200                 try {
201                     // Work around b/11446702 by clearing the test provider before removing it
202                     mLocationManager.clearTestProviderEnabled(provider);
203                     mLocationManager.removeTestProvider(provider);
204                 } catch (IllegalArgumentException e) {} // Not much to do about this
205             }
206             if(mAddedTestLocationProvider)
207             {
208                 removeTestLocationProvider();
209             }
210         }
211         LocationUtils.registerMockLocationProvider(
212                 InstrumentationRegistry.getInstrumentation(), false);
213 
214         if (mOnUiThread != null) {
215             mOnUiThread.cleanUp();
216         }
217     }
218 
addTestProviders()219     private void addTestProviders() {
220         Set<String> unavailableProviders = new HashSet<>();
221         for (String providerName : mProviders) {
222             LocationProvider provider = mLocationManager.getProvider(providerName);
223             if (provider == null) {
224                 unavailableProviders.add(providerName);
225                 continue;
226             }
227             mLocationManager.addTestProvider(provider.getName(),
228                     provider.requiresNetwork(), //requiresNetwork,
229                     provider.requiresSatellite(), // requiresSatellite,
230                     provider.requiresCell(),  // requiresCell,
231                     provider.hasMonetaryCost(), // hasMonetaryCost,
232                     provider.supportsAltitude(), // supportsAltitude,
233                     provider.supportsSpeed(), // supportsSpeed,
234                     provider.supportsBearing(), // supportsBearing,
235                     provider.getPowerRequirement(), // powerRequirement
236                     provider.getAccuracy()); // accuracy
237             mLocationManager.setTestProviderEnabled(provider.getName(), true);
238         }
239         mProviders.removeAll(unavailableProviders);
240     }
241 
242     private static final String TEST_PROVIDER_NAME = "location_provider_test";
243     private boolean mAddedTestLocationProvider = false;
244 
addTestLocationProvider()245     private void addTestLocationProvider() {
246         mLocationManager.addTestProvider(
247                 TEST_PROVIDER_NAME,
248                 true,  // requiresNetwork,
249                 false, // requiresSatellite,
250                 false, // requiresCell,
251                 false, // hasMonetaryCost,
252                 true,  // supportsAltitude,
253                 false, // supportsSpeed,
254                 true,  // supportsBearing,
255                 Criteria.POWER_MEDIUM,   // powerRequirement,
256                 Criteria.ACCURACY_FINE); // accuracy
257         mLocationManager.setTestProviderEnabled(TEST_PROVIDER_NAME, true);
258     }
259 
removeTestLocationProvider()260     private void removeTestLocationProvider() {
261         mLocationManager.clearTestProviderEnabled(TEST_PROVIDER_NAME);
262         mLocationManager.removeTestProvider(TEST_PROVIDER_NAME);
263     }
264 
startUpdateLocationThread()265     private void startUpdateLocationThread() {
266         // Only start the thread once
267         if (mLocationUpdateThread == null) {
268             mLocationUpdateThreadExitRequested = false;
269             mLocationUpdateThread = new Thread() {
270                 @Override
271                 public void run() {
272                     while (!mLocationUpdateThreadExitRequested) {
273                         try {
274                             Thread.sleep(LOCATION_THREAD_UPDATE_WAIT_MS);
275                         } catch (Exception e) {
276                             // Do nothing, an extra update is no problem
277                         }
278                         updateLocation();
279                     }
280                 }
281             };
282             mLocationUpdateThread.start();
283         }
284     }
285 
stopUpdateLocationThread()286     private void stopUpdateLocationThread() {
287         // Only stop the thread if it was started
288         if (mLocationUpdateThread != null) {
289             mLocationUpdateThreadExitRequested = true;
290             try {
291                 mLocationUpdateThread.join();
292             } catch (InterruptedException e) {
293                 // Do nothing
294             }
295             mLocationUpdateThread = null;
296         }
297     }
298 
299     // Update location with a fixed latitude and longtitude, sets the time to the current time.
updateLocation()300     private void updateLocation() {
301         for (int i = 0; i < mProviders.size(); i++) {
302             Location location = new Location(mProviders.get(i));
303             location.setLatitude(40);
304             location.setLongitude(40);
305             location.setAccuracy(1.0f);
306             location.setTime(java.lang.System.currentTimeMillis());
307             location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
308             mLocationManager.setTestProviderLocation(mProviders.get(i), location);
309         }
310     }
311 
312     // Need to set the location just after loading the url. Setting it after each load instead of
313     // using a maximum age.
loadUrlAndUpdateLocation(String url)314     private void loadUrlAndUpdateLocation(String url) {
315         mOnUiThread.loadUrlAndWaitForCompletion(url);
316         startUpdateLocationThread();
317     }
318 
319     // WebChromeClient that accepts each location for one load. WebChromeClient is used in
320     // WebViewOnUiThread to detect when the page is loaded, so subclassing the one used there.
321     private static class TestSimpleGeolocationRequestWebChromeClient
322                 extends WaitForProgressClient {
323         private boolean mReceivedRequest = false;
324         private final boolean mAccept;
325         private final boolean mRetain;
326 
TestSimpleGeolocationRequestWebChromeClient( WebViewOnUiThread webViewOnUiThread, boolean accept, boolean retain)327         public TestSimpleGeolocationRequestWebChromeClient(
328                 WebViewOnUiThread webViewOnUiThread, boolean accept, boolean retain) {
329             super(webViewOnUiThread);
330             this.mAccept = accept;
331             this.mRetain = retain;
332         }
333 
334         @Override
onGeolocationPermissionsShowPrompt( String origin, GeolocationPermissions.Callback callback)335         public void onGeolocationPermissionsShowPrompt(
336                 String origin, GeolocationPermissions.Callback callback) {
337             mReceivedRequest = true;
338             callback.invoke(origin, mAccept, mRetain);
339         }
340     }
341 
342     // Test loading a page and accepting the domain for one load
343     @Test
testSimpleGeolocationRequestAcceptOnce()344     public void testSimpleGeolocationRequestAcceptOnce() throws Exception {
345         final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptOnce =
346                 new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, false);
347         mOnUiThread.setWebChromeClient(chromeClientAcceptOnce);
348         loadUrlAndUpdateLocation(URL_1);
349         Callable<Boolean> receivedRequest = new Callable<Boolean>() {
350             @Override
351             public Boolean call() {
352                 return chromeClientAcceptOnce.mReceivedRequest;
353             }
354         };
355         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
356         Callable<Boolean> receivedLocation = new Callable<Boolean>() {
357             @Override
358             public Boolean call() {
359                 return mJavascriptStatusReceiver.mHasPosition;
360             }
361         };
362         PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
363         chromeClientAcceptOnce.mReceivedRequest = false;
364         // Load URL again, should receive callback again
365         loadUrlAndUpdateLocation(URL_1);
366         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
367         PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
368     }
369 
370     private static class OriginCheck extends PollingCheck implements
371             android.webkit.ValueCallback<Set<String>> {
372 
373         private boolean mReceived = false;
374         private final Set<String> mExpectedValue;
375         private Set<String> mReceivedValue = null;
376 
OriginCheck(Set<String> val)377         public OriginCheck(Set<String> val) {
378             mExpectedValue = val;
379         }
380 
381         @Override
check()382         protected boolean check() {
383             if (!mReceived) return false;
384             if (mExpectedValue.equals(mReceivedValue)) return true;
385             if (mExpectedValue.size() != mReceivedValue.size()) return false;
386             // Origins can have different strings even if they represent the same origin,
387             // for example http://www.example.com is the same origin as http://www.example.com/
388             // and they are both valid representations
389             for (String origin : mReceivedValue) {
390                 if (mExpectedValue.contains(origin)) continue;
391                 if (origin.endsWith("/")) {
392                     if (mExpectedValue.contains(origin.substring(0, origin.length() - 1))) {
393                         continue;
394                     }
395                 } else {
396                     if (mExpectedValue.contains(origin + "/")) continue;
397                 }
398                 return false;
399             }
400             return true;
401         }
402         @Override
onReceiveValue(Set<String> value)403         public void onReceiveValue(Set<String> value) {
404             mReceived = true;
405             mReceivedValue = value;
406         }
407     }
408 
409     // Class that waits and checks for a particular value being received
410     private static class BooleanCheck extends PollingCheck implements
411             android.webkit.ValueCallback<Boolean> {
412 
413         private boolean mReceived = false;
414         private final boolean mExpectedValue;
415         private boolean mReceivedValue;
416 
BooleanCheck(boolean val)417         public BooleanCheck(boolean val) {
418             mExpectedValue = val;
419         }
420 
421         @Override
check()422         protected boolean check() {
423             return mReceived && mReceivedValue == mExpectedValue;
424         }
425 
426         @Override
onReceiveValue(Boolean value)427         public void onReceiveValue(Boolean value) {
428             mReceived = true;
429             mReceivedValue = value;
430         }
431     }
432 
433     // Test loading a page and retaining the domain forever
434     @Test
testSimpleGeolocationRequestAcceptAlways()435     public void testSimpleGeolocationRequestAcceptAlways() throws Exception {
436         final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptAlways =
437                 new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, true);
438         mOnUiThread.setWebChromeClient(chromeClientAcceptAlways);
439         // Load url once, and the callback should accept the domain for all future loads
440         loadUrlAndUpdateLocation(URL_1);
441         Callable<Boolean> receivedRequest = new Callable<Boolean>() {
442             @Override
443             public Boolean call() {
444                 return chromeClientAcceptAlways.mReceivedRequest;
445             }
446         };
447         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
448         Callable<Boolean> receivedLocation = new Callable<Boolean>() {
449             @Override
450             public Boolean call() {
451                 return mJavascriptStatusReceiver.mHasPosition;
452             }
453         };
454         PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
455         chromeClientAcceptAlways.mReceivedRequest = false;
456         mJavascriptStatusReceiver.clearState();
457         // Load the same URL again
458         loadUrlAndUpdateLocation(URL_1);
459         PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
460         assertFalse("Prompt for geolocation permission should not be called the second time",
461                 chromeClientAcceptAlways.mReceivedRequest);
462         // Check that the permission is in GeolocationPermissions
463         BooleanCheck trueCheck = new BooleanCheck(true);
464         GeolocationPermissions.getInstance().getAllowed(URL_1, trueCheck);
465         trueCheck.run();
466         Set<String> acceptedOrigins = new TreeSet<String>();
467         acceptedOrigins.add(URL_1);
468         OriginCheck originCheck = new OriginCheck(acceptedOrigins);
469         GeolocationPermissions.getInstance().getOrigins(originCheck);
470         originCheck.run();
471 
472         // URL_2 should get a prompt
473         chromeClientAcceptAlways.mReceivedRequest = false;
474         loadUrlAndUpdateLocation(URL_2);
475         // Checking the callback for geolocation permission prompt is called
476         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
477         PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, receivedLocation);
478         acceptedOrigins.add(URL_2);
479         originCheck = new OriginCheck(acceptedOrigins);
480         GeolocationPermissions.getInstance().getOrigins(originCheck);
481         originCheck.run();
482         // Remove a domain manually that was added by the callback
483         GeolocationPermissions.getInstance().clear(URL_1);
484         acceptedOrigins.remove(URL_1);
485         originCheck = new OriginCheck(acceptedOrigins);
486         GeolocationPermissions.getInstance().getOrigins(originCheck);
487         originCheck.run();
488     }
489 
490     // Test the GeolocationPermissions API
491     @Test
testGeolocationPermissions()492     public void testGeolocationPermissions() {
493         Set<String> acceptedOrigins = new TreeSet<String>();
494         BooleanCheck falseCheck = new BooleanCheck(false);
495         GeolocationPermissions.getInstance().getAllowed(URL_2, falseCheck);
496         falseCheck.run();
497         OriginCheck originCheck = new OriginCheck(acceptedOrigins);
498         GeolocationPermissions.getInstance().getOrigins(originCheck);
499         originCheck.run();
500 
501         // Remove a domain that has not been allowed
502         GeolocationPermissions.getInstance().clear(URL_2);
503         acceptedOrigins.remove(URL_2);
504         originCheck = new OriginCheck(acceptedOrigins);
505         GeolocationPermissions.getInstance().getOrigins(originCheck);
506         originCheck.run();
507 
508         // Add a domain
509         acceptedOrigins.add(URL_2);
510         GeolocationPermissions.getInstance().allow(URL_2);
511         originCheck = new OriginCheck(acceptedOrigins);
512         GeolocationPermissions.getInstance().getOrigins(originCheck);
513         originCheck.run();
514         BooleanCheck trueCheck = new BooleanCheck(true);
515         GeolocationPermissions.getInstance().getAllowed(URL_2, trueCheck);
516         trueCheck.run();
517 
518         // Add a domain
519         acceptedOrigins.add(URL_1);
520         GeolocationPermissions.getInstance().allow(URL_1);
521         originCheck = new OriginCheck(acceptedOrigins);
522         GeolocationPermissions.getInstance().getOrigins(originCheck);
523         originCheck.run();
524 
525         // Remove a domain that has been allowed
526         GeolocationPermissions.getInstance().clear(URL_2);
527         acceptedOrigins.remove(URL_2);
528         originCheck = new OriginCheck(acceptedOrigins);
529         GeolocationPermissions.getInstance().getOrigins(originCheck);
530         originCheck.run();
531         falseCheck = new BooleanCheck(false);
532         GeolocationPermissions.getInstance().getAllowed(URL_2, falseCheck);
533         falseCheck.run();
534 
535         // Try to clear all domains
536         GeolocationPermissions.getInstance().clearAll();
537         acceptedOrigins.clear();
538         originCheck = new OriginCheck(acceptedOrigins);
539         GeolocationPermissions.getInstance().getOrigins(originCheck);
540         originCheck.run();
541 
542         // Add a domain
543         acceptedOrigins.add(URL_1);
544         GeolocationPermissions.getInstance().allow(URL_1);
545         originCheck = new OriginCheck(acceptedOrigins);
546         GeolocationPermissions.getInstance().getOrigins(originCheck);
547         originCheck.run();
548     }
549 
550     // Test loading pages and checks rejecting once and rejecting the domain forever
551     @Test
testSimpleGeolocationRequestReject()552     public void testSimpleGeolocationRequestReject() throws Exception {
553         final TestSimpleGeolocationRequestWebChromeClient chromeClientRejectOnce =
554                 new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, false, false);
555         mOnUiThread.setWebChromeClient(chromeClientRejectOnce);
556         // Load url once, and the callback should reject it once
557         mOnUiThread.loadUrlAndWaitForCompletion(URL_1);
558         Callable<Boolean> receivedRequest = new Callable<Boolean>() {
559             @Override
560             public Boolean call() {
561                 return chromeClientRejectOnce.mReceivedRequest;
562             }
563         };
564         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
565         Callable<Boolean> locationDenied = new Callable<Boolean>() {
566             @Override
567             public Boolean call() {
568                 return mJavascriptStatusReceiver.mDenied;
569             }
570         };
571         PollingCheck.check("JS got position", POLLING_TIMEOUT, locationDenied);
572         // Same result should happen on next run
573         chromeClientRejectOnce.mReceivedRequest = false;
574         mOnUiThread.loadUrlAndWaitForCompletion(URL_1);
575         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
576         PollingCheck.check("JS got position", POLLING_TIMEOUT, locationDenied);
577 
578         // Try to reject forever
579         final TestSimpleGeolocationRequestWebChromeClient chromeClientRejectAlways =
580             new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, false, true);
581         mOnUiThread.setWebChromeClient(chromeClientRejectAlways);
582         mOnUiThread.loadUrlAndWaitForCompletion(URL_2);
583         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
584         PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
585         // second load should now not get a prompt
586         chromeClientRejectAlways.mReceivedRequest = false;
587         mOnUiThread.loadUrlAndWaitForCompletion(URL_2);
588         PollingCheck.check("JS didn't get position", POLLING_TIMEOUT, locationDenied);
589         PollingCheck.check("Geolocation prompt not called", POLLING_TIMEOUT, receivedRequest);
590 
591         // Test if it gets added to origins
592         Set<String> acceptedOrigins = new TreeSet<String>();
593         acceptedOrigins.add(URL_2);
594         OriginCheck domainCheck = new OriginCheck(acceptedOrigins);
595         GeolocationPermissions.getInstance().getOrigins(domainCheck);
596         domainCheck.run();
597         // And now check that getAllowed returns false
598         BooleanCheck falseCheck = new BooleanCheck(false);
599         GeolocationPermissions.getInstance().getAllowed(URL_1, falseCheck);
600         falseCheck.run();
601     }
602 
603     // Test deny geolocation on insecure origins
604     @Test
testGeolocationRequestDeniedOnInsecureOrigin()605     public void testGeolocationRequestDeniedOnInsecureOrigin() throws Exception {
606         final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptAlways =
607                 new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, true);
608         mOnUiThread.setWebChromeClient(chromeClientAcceptAlways);
609         loadUrlAndUpdateLocation(URL_INSECURE);
610         Callable<Boolean> locationDenied = new Callable<Boolean>() {
611             @Override
612             public Boolean call() {
613                 return mJavascriptStatusReceiver.mDenied;
614             }
615         };
616         PollingCheck.check("JS got position", POLLING_TIMEOUT, locationDenied);
617         assertFalse("The geolocation permission prompt should not be called",
618                 chromeClientAcceptAlways.mReceivedRequest);
619     }
620 
621     // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
622     // notify back to Java when a location or error is received.
623     public final static class JavascriptStatusReceiver {
624         public volatile boolean mHasPosition = false;
625         public volatile boolean mDenied = false;
626         public volatile boolean mUnavailable = false;
627         public volatile boolean mTimeout = false;
628 
clearState()629         public void clearState() {
630             mHasPosition = false;
631             mDenied = false;
632             mUnavailable = false;
633             mTimeout = false;
634         }
635 
636         @JavascriptInterface
errorDenied()637         public void errorDenied() {
638             mDenied = true;
639         }
640 
641         @JavascriptInterface
errorUnavailable()642         public void errorUnavailable() {
643             mUnavailable = true;
644         }
645 
646         @JavascriptInterface
errorTimeout()647         public void errorTimeout() {
648             mTimeout = true;
649         }
650 
651         @JavascriptInterface
gotLocation()652         public void gotLocation() {
653             mHasPosition = true;
654         }
655     }
656 }
657