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