Skip to content

Commit

Permalink
Add a mechanism to pause and resume geolocation requests. Currently A…
Browse files Browse the repository at this point in the history
…ndroid WebView is the only platform to make use of this new API. We implement this as a new API on the Browser-side GeolocationDispatcherHost.

In doing so:
 - Refactor the current LocationProvider such that it doesn't rely on ActivityStatus, updating LocationProviderTest accordingly
 - Introduce ContentViewLocationTest.java that verifies the LocationProvider implementation is paused and resumed when the ContentView is hidde/shown
 - Introduce a AwGeolocationTest that verifies the LocationProvider implementation is paused and resumed when the new API is invoked
 - Introduce LocationProviderFactory and a MockLocationProvider to avoid relying on the system location provider when running tests, as it's not possible to enable mock locations on Android user builds without physical access to the device.

BUG=b/11336074

Review URL: https://codereview.chromium.org/65273002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@240719 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
benm@chromium.org committed Dec 13, 2013
1 parent 139574d commit 36d6a43
Show file tree
Hide file tree
Showing 17 changed files with 855 additions and 280 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.android_webview.test;

import android.test.suitebuilder.annotation.MediumTest;
import android.webkit.GeolocationPermissions;

import org.chromium.android_webview.AwContents;
import org.chromium.base.test.util.Feature;
import org.chromium.content.browser.LocationProviderFactory;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.MockLocationProvider;

/**
* Test suite for Geolocation in AwContents. Smoke tests for
* basic functionality, and tests to ensure the AwContents.onPause
* and onResume APIs affect Geolocation as expected.
*/
public class GeolocationTest extends AwTestBase {

private static final long TEST_TIMEOUT_MS = 5000L;
private static final int CHECK_INTERVAL_MS = 100;

private TestAwContentsClient mContentsClient;
private AwContents mAwContents;
private MockLocationProvider mMockLocationProvider;

private static final String RAW_HTML =
"<!DOCTYPE html>\n" +
"<html>\n" +
" <head>\n" +
" <title>Geolocation</title>\n" +
" <script>\n" +
" var positionCount = 0;\n" +
" function gotPos(position) {\n" +
" positionCount++;\n" +
" }\n" +
" function initiate_getCurrentPosition() {\n" +
" navigator.geolocation.getCurrentPosition(\n" +
" gotPos, function() { }, { });\n" +
" }\n" +
" function initiate_watchPosition() {\n" +
" navigator.geolocation.watchPosition(\n" +
" gotPos, function() { }, { });\n" +
" }\n" +
" </script>\n" +
" </head>\n" +
" <body>\n" +
" </body>\n" +
"</html>";

@Override
public void setUp() throws Exception {
super.setUp();
mContentsClient = new TestAwContentsClient() {
@Override
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
callback.invoke(origin, true, true);
}
};
mAwContents = createAwTestContainerViewOnMainSync(mContentsClient).getAwContents();
enableJavaScriptOnUiThread(mAwContents);
setupGeolocation();
}

@Override
public void tearDown() throws Exception {
mMockLocationProvider.stopUpdates();
GeolocationPermissions.getInstance().clearAll();
super.tearDown();
}

private void setupGeolocation() {
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAwContents.getSettings().setGeolocationEnabled(true);
}
});
mMockLocationProvider = new MockLocationProvider();
LocationProviderFactory.setLocationProviderImpl(mMockLocationProvider);
}

private int getPositionCountFromJS() {
int result = -1;
try {
result = Integer.parseInt(executeJavaScriptAndWaitForResult(
mAwContents, mContentsClient, "positionCount"));
} catch (Exception e) {
fail("Unable to get positionCount");
}
return result;
}

private void ensureGeolocationRunning(final boolean running) throws Exception {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return mMockLocationProvider.isRunning() == running;
}
}, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS));
}


/**
* Ensure that a call to navigator.getCurrentPosition works in WebView.
*/
@MediumTest
@Feature({"AndroidWebView"})
public void testGetPosition() throws Throwable {
loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
RAW_HTML, "text/html", false);

mAwContents.evaluateJavaScript("initiate_getCurrentPosition();", null);

assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getPositionCountFromJS() == 1;
}
}, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS));

mAwContents.evaluateJavaScript("initiate_getCurrentPosition();", null);
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getPositionCountFromJS() == 2;
}
}, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS));
}

/**
* Ensure that a call to navigator.watchPosition works in WebView.
*/
@MediumTest
@Feature({"AndroidWebView"})
public void testWatchPosition() throws Throwable {
loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
RAW_HTML, "text/html", false);

mAwContents.evaluateJavaScript("initiate_watchPosition();", null);

assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getPositionCountFromJS() > 1;
}
}, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS));
}

@MediumTest
@Feature({"AndroidWebView"})
public void testPauseGeolocationOnPause() throws Throwable {
// Start a watch going.
loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
RAW_HTML, "text/html", false);

mAwContents.evaluateJavaScript("initiate_watchPosition();", null);

assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getPositionCountFromJS() > 1;
}
}, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS));

ensureGeolocationRunning(true);

getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAwContents.onPause();
}
});

ensureGeolocationRunning(false);

try {
executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, "positionCount = 0");
} catch (Exception e) {
fail("Unable to clear positionCount");
}
assertEquals(0, getPositionCountFromJS());

getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAwContents.onResume();
}
});

ensureGeolocationRunning(true);

assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getPositionCountFromJS() > 1;
}
}, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS));
}

@MediumTest
@Feature({"AndroidWebView"})
public void testPauseAwContentsBeforeNavigating() throws Throwable {
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAwContents.onPause();
}
});

// Start a watch going.
loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
RAW_HTML, "text/html", false);

mAwContents.evaluateJavaScript("initiate_watchPosition();", null);

assertEquals(0, getPositionCountFromJS());

ensureGeolocationRunning(false);

getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAwContents.onResume();
}
});

ensureGeolocationRunning(true);

assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getPositionCountFromJS() > 1;
}
}, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS));

}

@MediumTest
@Feature({"AndroidWebView"})
public void testResumeWhenNotStarted() throws Throwable {
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAwContents.onPause();
}
});

loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
RAW_HTML, "text/html", false);

getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mAwContents.onResume();
}
});

ensureGeolocationRunning(false);
}

}
10 changes: 6 additions & 4 deletions android_webview/native/aw_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,13 @@ void AwContents::SetWindowVisibility(JNIEnv* env, jobject obj, bool visible) {
void AwContents::SetIsPaused(JNIEnv* env, jobject obj, bool paused) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
browser_view_renderer_->SetIsPaused(paused);
if (paused) {
ContentViewCore* cvc =
ContentViewCore::FromWebContents(web_contents_.get());
if (cvc)
ContentViewCore* cvc =
ContentViewCore::FromWebContents(web_contents_.get());
if (cvc) {
cvc->PauseOrResumeGeolocation(paused);
if (paused) {
cvc->PauseVideo();
}
}
}

Expand Down
28 changes: 26 additions & 2 deletions content/browser/android/content_view_core_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ ScopedJavaLocalRef<jobject> CreateJavaRect(
static_cast<int>(rect.y()),
static_cast<int>(rect.right()),
static_cast<int>(rect.bottom())));
};
}

} // namespace

// Enables a callback when the underlying WebContents is destroyed, to enable
Expand Down Expand Up @@ -173,7 +174,8 @@ ContentViewCoreImpl::ContentViewCoreImpl(JNIEnv* env, jobject obj,
kDefaultVSyncIntervalMicros * kDefaultBrowserCompositeVSyncFraction)),
view_android_(view_android),
window_android_(window_android),
device_orientation_(0) {
device_orientation_(0),
geolocation_needs_pause_(false) {
CHECK(web_contents) <<
"A ContentViewCoreImpl should be created with a valid WebContents.";

Expand Down Expand Up @@ -263,6 +265,8 @@ void ContentViewCoreImpl::Observe(int type,
}
}
SetFocusInternal(HasFocus());
if (geolocation_needs_pause_)
PauseOrResumeGeolocation(true);
break;
}
case NOTIFICATION_RENDERER_PROCESS_CREATED: {
Expand Down Expand Up @@ -348,6 +352,26 @@ void ContentViewCoreImpl::PauseVideo() {
host->Send(new ViewMsg_PauseVideo(host->GetRoutingID()));
}

void ContentViewCoreImpl::PauseOrResumeGeolocation(bool should_pause) {
geolocation_needs_pause_ = should_pause;
RenderViewHostImpl* rvh =
static_cast<RenderViewHostImpl*>(web_contents_->GetRenderViewHost());
if (rvh) {
scoped_refptr<GeolocationDispatcherHost> geolocation_dispatcher =
static_cast<RenderProcessHostImpl*>(
web_contents_->GetRenderProcessHost())->
geolocation_dispatcher_host();
if (geolocation_dispatcher.get()) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&GeolocationDispatcherHost::PauseOrResume,
geolocation_dispatcher,
rvh->GetRoutingID(),
should_pause));
geolocation_needs_pause_ = false;
}
}
}

void ContentViewCoreImpl::OnTabCrashed() {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
Expand Down
3 changes: 3 additions & 0 deletions content/browser/android/content_view_core_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class ContentViewCoreImpl : public ContentViewCore,
virtual void RequestContentClipping(const gfx::Rect& clipping,
const gfx::Size& content_size) OVERRIDE;
virtual void PauseVideo() OVERRIDE;
virtual void PauseOrResumeGeolocation(bool should_pause) OVERRIDE;

// --------------------------------------------------------------------------
// Methods called from Java via JNI
Expand Down Expand Up @@ -379,6 +380,8 @@ class ContentViewCoreImpl : public ContentViewCore,
// will be sent to Renderer once it is ready.
int device_orientation_;

bool geolocation_needs_pause_;

DISALLOW_COPY_AND_ASSIGN(ContentViewCoreImpl);
};

Expand Down
Loading

0 comments on commit 36d6a43

Please sign in to comment.